Project import generated by Copybara. NOKEYCHECK=True GitOrigin-RevId: 5bcbb79408d13ec760f88d0af3a7adebf08c4bb4
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af205ec --- /dev/null +++ b/.gitignore
@@ -0,0 +1,164 @@ +*.o +*.a +*.lo +*.la +*.so +*.gcno +*.gcda +.deps +.libs +.dirstamp +Makefile +Makefile.in +aclocal.m4 +config.guess +config.h +config.h.in +config.h.in~ +config.log +config.status +config.sub +configure +depcomp +compile +install-sh +libtool +ltmain.sh +missing +stamp-h1 +autom4te.cache +test-driver +test-suite.log +coverage.info + +coverage +lib/bluez.pc +lib/bluetooth +src/builtin.h +src/bluetoothd +tools/97-hid2hci.rules + +profiles/cups/bluetooth +profiles/iap/iapd + +attrib/gatttool +tools/avinfo +tools/bccmd +tools/hwdb +tools/ciptool +tools/hciattach +tools/hciconfig +tools/hcieventmask +tools/hcisecfilter +tools/hcitool +tools/hcidump +tools/hid2hci +tools/rfcomm +tools/l2ping +tools/l2test +tools/cltest +tools/rctest +tools/scotest +tools/amptest +tools/oobtest +tools/advtest +tools/sdptool +tools/avtest +tools/bdaddr +tools/bluemoon +tools/seq2bseq +tools/hex2hcd +tools/nokfw +tools/btiotest +tools/mpris-proxy +tools/bluetooth-player +tools/l2cap-tester +tools/sco-tester +tools/hci-tester +tools/eddystone +tools/ibeacon +tools/btproxy +tools/btinfo +tools/3dsp +tools/obexctl +tools/gatt-service +tools/btgatt-client +tools/btgatt-server +tools/create-image +tools/test-runner +tools/check-selftest +tools/mcaptest +tools/bneptest +test/sap_client.pyc +test/bluezutils.pyc +unit/test-ringbuf +unit/test-queue +unit/test-eir +unit/test-uuid +unit/test-crc +unit/test-textfile +unit/test-gdbus-client +unit/test-sdp +unit/test-lib +unit/test-mgmt +unit/test-uhid +unit/test-hfp +unit/test-crypto +unit/test-ecc +unit/test-hog +tools/mgmt-tester +tools/smp-tester +tools/gap-tester +tools/rfcomm-tester +tools/bnep-tester +tools/userchan-tester +tools/btattach +tools/btconfig +tools/btmgmt +tools/btsnoop +peripheral/btsensor +monitor/btmon +emulator/btvirt +emulator/b1ee +emulator/hfp +client/bluetoothctl +mesh/meshctl + +src/bluetoothd.8 +src/bluetooth.service + +obexd/src/builtin.h +obexd/src/obexd +obexd/src/obex.service +tools/obex-client-tool +tools/obex-server-tool +unit/test-gobex +unit/test-gobex-apparam +unit/test-gobex-header +unit/test-gobex-packet +unit/test-gobex-transfer +unit/test-avdtp +unit/test-avctp +unit/test-avrcp +unit/test-gatt +unit/test-midi +unit/test-gattrib +unit/test-*.log +unit/test-*.trs + +doc/btmon.1 + +android/system-emulator +android/bluetoothd +android/avdtptest +android/haltest +android/android-tester +android/ipc-tester +android/bluetoothd-snoop +android/test-ipc +android/test-*.log +android/test-*.trs + +cscope.in.out +cscope.out +cscope.po.out
diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..a7e36e3 --- /dev/null +++ b/.mailmap
@@ -0,0 +1,11 @@ +Luiz Augusto von Dentz <luiz.dentz-von@nokia.com> <luiz.dentz-von@nokia.com> +Vinicius Costa Gomes <vinicius.gomes@openbossa.org> <vinicius.gomes@openbossa.org> +Elvis Pfützenreuter <epx@signove.com> <epx@signove.com> +Santiago Carot-Nemesio <scarot@libresoft.es> <scarot@libresoft.es> +José Antonio Santos Cadenas <santoscadenas@gmail.com> <santoscadenas@gmail.com> +Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com> <Waldemar.Rymarkiewicz@tieto.com> +Alok Barsode <alokbarsode@gmail.com> <alok@greatbear.(none)> +André Dieb Martins <andre.dieb@signove.com> <andre.dieb@signove.com> +Tedd Ho-Jeong An <tedd.an@intel.com> <tedd.an@intel.com> +Martin Xu <martin.xu@linux.intel.com> <martin.xu@linux.intel.com> +Marie Janssen <jamuraa@chromium.org> <jamuraa@chromium.org>
diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..176b55b --- /dev/null +++ b/AUTHORS
@@ -0,0 +1,102 @@ +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@intel.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 Padovan <gustavo@padovan.org> +Marc-Andre Lureau <marc-andre.lureau@nokia.com> +Bea Lam <bea.lam@nokia.com> +Zygo Blaxell <zygo.blaxell@xandros.com> +Forrest Zhao <forrest.zhao@intel.com> +Scott Talbot <psyc@stalbot.com> +Ilya Rubtsov <lusyaru@gmail.com> +Mario Limonciello <mario_limonciello@dell.com> +Filippo Giunchedi <filippo@esaurito.net> +Jaikumar Ganesh <jaikumar@google.com> +Elvis Pfutzenreuter <epx@signove.com> +Santiago Carot-Nemesio <scarot@libresoft.es> +José Antonio Santos Cadenas <jcaden@libresoft.es> +Francisco Alecrim <francisco.alecrim@openbossa.org> +Daniel Orstadius <daniel.orstadius@gmail.com> +Anderson Briglia <anderson.briglia@openbossa.org> +Anderson Lizardo <anderson.lizardo@openbossa.org> +Bruna Moreira <bruna.moreira@openbossa.org> +Brian Gix <bgix@codeaurora.org> +Andre Guedes <andre.guedes@openbossa.org> +Sheldon Demario <sheldon.demario@openbossa.org> +Lucas De Marchi <lucas.demarchi@profusion.mobi> +Szymon Janc <szymon.janc@codecoup.pl> +Syam Sidhardhan <s.syam@samsung.com> +Paulo Alcantara <pcacjr@gmail.com> +Jefferson Delfes <jefferson.delfes@openbossa.org> +Andrzej Kaczmarek <andrzej.kaczmarek@codecoup.pl> +Eder Ruiz Maria <eder.ruiz@openbossa.org> +Mikel Astiz <mikel.astiz@bmw-carit.de> +Chan-yeol Park <chanyeol.park@samsung.com> +João Paulo Rechi Vita <jprvita@gmail.com> +Larry Junior <larry.junior@openbossa.org> +Raymond Liu <raymond.liu@intel.com> +Radoslaw Jablonski <ext-jablonski.radoslaw@nokia.com> +Rafal Michalski <michalski.raf@gmail.com> +Dmitriy Paliy <dmitriy.paliy@nokia.com> +Bartosz Szatkowski <bulislaw@linux.com> +Lukasz Pawlik <lucas.pawlik@gmail.com> +Slawomir Bochenski <lkslawek@gmail.com> +Wayne Lee <waynelee@qualcomm.com> +Ricky Yuen <ryuen@qualcomm.com> +Takashi Sasai <sasai@sm.sony.co.jp> +Andre Dieb Martins <andre.dieb@signove.com> +Cristian Rodríguez <crrodriguez@opensuse.org> +Alex Deymo <deymo@chromium.org> +Petri Gynther <pgynther@google.com> +Scott James Remnant <scott@netsplit.com> +Jakub Tyszkowski <jakub.tyszkowski@tieto.com> +Grzegorz Kołodziejczyk <grzegorz.kolodziejczyk@tieto.com> +Marcin Krąglak <marcin.kraglak@tieto.com> +Łukasz Rymanowski <lukasz.rymanowski@codecoup.pl> +Jerzy Kasenberg <jerzy.kasenberg@tieto.com> +Arman Uguray <armansito@chromium.org> +Artem Rakhov <arakhov@chromium.org> +Mike Ryan <mikeryan@lacklustre.net> +David Herrmann <dh.herrmann@gmail.com> +Jacob Siverskog <jacob@teenageengineering.com> +Sebastian Chłąd <sebastian.chlad@tieto.com> +Alex Gal <a.gal@miip.ca> +Loic Poulain <loic.poulain@intel.com> +Gowtham Anandha Babu <gowtham.ab@samsung.com> +Bharat Panda <bharat.panda@samsung.com> +Marie Janssen <jamuraa@chromium.org> +Jaganath Kanakkassery <jaganath.k@samsung.com> +Michał Narajowski <michal.narajowski@codecoup.pl>
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..e187ba7 --- /dev/null +++ b/ChangeLog
@@ -0,0 +1,2214 @@ +ver 5.47: + Fix issue with handling AcquireNotify registration. + Fix issue with handling support for reconnection interval. + Fix issue with handling A2DP transport and accepting streams. + Fix issue with fallback from BR/EDR to LE bearer handling. + Add support for appearance and local name advertising data. + Add support for retrieving the supported discovery filters. + Add support for decoding Bluetooth 5.0 commands and events. + Add support for decoding Bluetooth Mesh advertising bearer. + Add support for Bluetooth Mesh control application. + +ver 5.46: + Fix issue with handling ATT over BR/EDR connections. + Fix issue with SDP browsing cleanup after connection. + Fix issue with pointer dereference and OPP Put request. + Fix issue with identity address updates during pairing. + Fix issue with not removing services that had disappeared. + Add support for improved discovery of included services. + Add support for simplified characteristics discovery. + Add support for GATT caching configuration option. + Add experimental support for AcquireWrite and AcquireNotify. + +ver 5.45: + Fix issue with agent support in Bluetooth client tool. + Fix issue with handling re-connection policy. + Fix issue with handling unknown ATT commands. + Fix issue with handling GATT Service Includes property. + Fix issue with handling PullAll for OBEX transfers. + Fix issue with handling delay in AVDTP Suspend responses. + Fix issue with handling decoding of management frames. + Add support for frame counters in Bluetooth monitor tool. + +ver 5.44: + Fix issue with GAP and GATT service registration. + Fix issue with wrong address type for ATT sockets. + Fix issue with dictionary entries for advertising. + Fix issue with device information and HID over GATT. + Fix issue with handling secondary service discovery. + Fix issue with handling Attribute Read Long procedure. + Fix issue with handling Attribute Write Long procedure. + Fix issue with handling abort of AVDTP SetConfiguration. + Add support for single-mode static address configuration. + Add support for MIDI over Bluetooth Low Energy. + +ver 5.43: + Fix issue with HID over GATT support. + Fix issue with ATT Find By Type response handling. + Fix issue with handling insufficient authentication. + Fix issue with bonding while pairing is in progress. + Fix issue with BR/EDR pairing for dual-mode devices. + Fix issue with handling profile policy resets. + Fix issue with connecting state of services. + Fix issue with handling PAN GN Master role. + Add support for enabling LE Privacy feature. + +ver 5.42: + Fix issue with PBAP call logs from different folders. + Fix issue with OBEX over L2CAP and PowerPC architecture. + Fix issue with BR/EDR over LE selection during discovery. + Fix issue with selection of bearer after bonding. + Fix issue with handling socket recv() return values. + Fix issue with setting connecting service state. + Fix issue with setting correct ATT default MTU value. + Fix issue with not setting AVRCP player identifier. + Fix issue with handling AVRCP browsable player. + Fix issue with addressing AVRCP player changes. + Add support for new management tracing capability. + Mark GATT D-Bus APIs as stable interfaces. + +ver 5.41: + Fix issue with service state changes handling. + Fix issue with AVRCP and no available player. + Fix issue with handling discovery filters. + Fix issue with handling temporary addresses. + Fix issue with GATT MTU size and BR/EDR links. + Fix issue with OBEX and creating directories. + +ver 5.40: + Fix issue with not storing GATT attributes. + Fix issue with optional GATT notifications. + Fix issue with reading GATT extended properties. + Fix issue with GATT device name properties. + Fix issue with previously paired devices. + Fix issue with handling device removal. + Fix issue with profile connection handling. + Add support for TTY monitor protocol. + +ver 5.39: + Fix issue with missing uHID kernel support. + Fix issue with GATT reliable write handling. + Fix issue with GATT service changed handling. + Fix issue with GATT execute write handling. + Fix issue with AVRCP player event handling. + Fix issue with AVRCP controller handling. + Fix issue with AVDTP connection handling. + Fix issue with AVDTP error handling. + +ver 5.38: + Fix issue with stack overflow and UUID handling. + Fix issue with ObjectManager interface and GATT. + Fix issue with GATT database and error handling. + Fix issue with GATT client notifications. + Fix issue with GATT object ordering. + Fix issue with GATT default MTU exchange. + Fix issue with device attribute clearing. + Fix issue with AVRCP capabilities request. + +ver 5.37: + Fix issue with registering external profiles. + Fix issue with connecting external profiles. + Fix issue with GATT service changed handling. + Fix issue with not emitting GattServices update. + Convert to unified HID over GATT profile support. + Convert to KeyboardDisplay as default IO capability. + Install btattach utility by default. + +ver 5.36: + Fix issue with PBAP headers for size query. + Fix issue with AVRCP current player handling. + Fix issue with device information handling. + Fix issue with device disconnect handling. + Fix issue with duplicate connect handling. + Fix issue with attribute claiming for drivers. + +ver 5.35: + Fix issue with connected devices after discovery. + Fix issue with profile support and LTK loading. + Fix issue with AVRCP events for volume control. + Fix issue with OBEX session owner handling. + Fix issue with HID over GATT setup failures. + Fix issue with GATT notification registration. + Fix issue with GATT cache validation feature. + Add support for persistent GATT database. + Add support for controller enabling option. + +ver 5.34: + Fix issue with GATT profiles and auto-connect. + Fix issue with missing GoepL2CapPsm SDP data. + Fix issue with suspending AVDTP endpoints. + Fix issue with audio service state on disconnect. + Add support for AVRCP Set Addressed Player feature. + Add support for AVRCP Get Folder Items feature. + Add support for Android 5.1 HFP WBS callbacks. + +ver 5.33: + Fix issue with memory leak in GATT database. + Fix issue with AVDTP set configuration handling. + Fix issue with AVDTP discover procedure. + Fix issue with not emitting Paired property. + +ver 5.32: + Fix issue with OPP GET request path handling. + Fix issue with ATT information request errors. + Fix issue with advertising instance numbers. + Fix issue with overwriting SDP record cache. + Fix issue with new connections during disconnect. + Add support for GATT security auto-elevation. + +ver 5.31: + Fix issue with crash in networking interface. + Fix issue with crash when creating endless GATT loops. + Fix issue with memory leak when connecting services. + Fix issue with memory leak creating new D-Bus proxy. + Fix issue with profile connections from remote devices. + Fix issue with GATT over BR/EDR and MTU notification. + Fix issue with HID and dual mode remote devices. + Fix issue with handling A2DP vendor codec setup. + Fix issue with AVRCP and syncing player state. + Fix issue with GATT secondary discovery handling. + Fix issue with wrong characteristic allocation. + Add support for handling BNEP setup response. + Add support for setting GATT database security flags. + Add support for setting discovery filters interface. + Add support for user controlled advertising interface. + Update Android qualification documentation to PTS 6.1 release. + +ver 5.30: + Fix compilation error in C++ due to inline function. + Fix issue with missing storage of device information. + Fix issue with GATT client and gaps in service handles. + Fix issue with AVDTP discovery callback crashing. + Fix issue with AVCTP channel handling in case of conflicts. + Fix issue with AVRCP target and get capabilities command. + Add experimental support for LE advertising manager API. + Add support for Android 5.1 GATT MTU exchange API. + +ver 5.29: + Fix issue with AVCTP initial key repeat timeout. + Fix issue with Android application disconnect handling. + Fix issue with Android support and service notifications. + Fix issue with Android support and Exchange MTU Request. + Fix issue with Android HFP support and AT+CMER handling. + Fix issue with Android HFP support and SLC setup. + Fix issue with Android HFP support and call hold status. + Fix issue with Android HFP support and indicator handling. + Fix issue with Android HFP support and SCO/eSCO disconnection. + Fix issue with Android HID over GATT support and battery service. + Fix issue with GATT sending Exchange MTU Request for BR/EDR. + Fix issue with GATT notification support without CCC. + Fix issue with GATT object life-time after disconnects. + Fix issue with GATT notification handling API. + Add experimental support for GATT client D-Bus API. + Add experimental support for GATT server D-Bus API. + Add support for Multi Profile Specification. + Update Android qualification documentation to PTS 6.0 release. + +ver 5.28: + Fix issue with GATT device discovery and probing. + Fix issue with bearer selection for dual-mode devices. + Fix issue with device removal while connected. + Fix issue with device name setting from inquiry response. + Fix issue with missing termination of name characteristic. + Fix issue with UTF-8 length handling for device name. + Fix issue with AVCTP key auto release handling. + Fix issue with AVCTP key press repetition handling. + Fix issue with payload sizes and GATT notifications. + Fix issue with memory corruption and GATT notifications. + Add support for HID proxy switching and CSR 8510 A10 devices. + Add support for Broadcom hex2hcd conversion utility. + +ver 5.27: + Fix issue with endian handling and management interface. + Fix issue with pending GATT operations when disconnecting. + Fix issue with 128-bit UUID conversions for HID over GATT. + Add support for Android 5.0 SELinux policies. + +ver 5.26: + Fix issue with handling A2DP XCASE connection state. + Fix issue with crash and A2DP configuration failures. + Fix issue with crash during OBEX session shutdown. + Add support for version 1.2 of Phonebook Access Profile. + Add support for HID over GATT get and set report handling. + Add support for Low Energy Secure Connections feature. + Add support for Bluetooth 4.2 commands and events. + Add support for Android 5.0 Bluetooth features. + +ver 5.25: + Fix issue with SCO connection after codec negotiation. + Fix issue with GATT and secondary service discovery. + Fix issue with GATT write descriptor callback. + Fix issue with MAP supported features bits. + Add support for MAP local time and timezone offset. + Add support for PBAP speed-dial and favorites folders. + Add support for PBAP speed-dial and identifier filters. + Add support for controller mode configuration option. + Add initial support for Android Lollipop features. + +ver 5.24: + Fix issue with storing of connection parameters. + Add support for Phonebook Access Profile 1.2 features. + Add support for Message Access Profile 1.2 event reports. + Add support for Android Bluetooth configuration options. + +ver 5.23: + Fix issue with concurrent authorization requests. + Fix issue with HID report identifier mismatch. + Fix issue with crash when receiving uHID events. + Fix issue with crash and OBEX disconnect handling. + Fix issue with OBEX client transfers and suspend. + Fix issue with parsing of MAP application parameters. + Fix issue with devices rejecting AVRCP GetCapabilities. + Add support for kernel whitelist and Android Bluetooth. + +ver 5.22: + Fix issue with UHID_OUTPUT events mapping. + Fix issue with UHID_FEATURE events handling. + Fix issue with UINT32_MAX overflow and AVRCP. + Fix issue when dirent type DT_UNKNOWN is returned. + Add support for kernel whitelist filtering feature. + Add support for Android Bluetooth GATT over BR/EDR. + +ver 5.21: + Fix issue with SDP requests and wrong PDU size. + Fix issue with handling passive scanning triggers. + Add support for storing and loading connection parameters. + Add support for kernel background auto-connection feature. + Add support for Android Bluetooth Scan Parameters feature. + Add support for Android Bluetooth Device Information feature. + Add support for Android Bluetooth Health Device interface. + +ver 5.20: + Fix issue with LED handling of PS3 controllers. + Add support for Android Bluetooth GATT server interface. + Add support for Android Bluetooth HID over GATT feature. + Add support for Android Bluetooth multi-profile feature. + Add support for Android Bluetooth aptX audio integration. + + Note: aptX codec not included + +ver 5.19: + Fix issue with OBEX Put-Delete and Create-Empty methods. + Fix issue with AVRCP browsable/searchable player properties. + Fix issue with handling multiple default agents. + Fix issue with handling unpair event per bearer. + Fix issue with HID over GATT report ID presence. + Add support for HID protocol handling in userspace. + Add support for Bluetooth reconnection policy framework. + Add support for Android Bluetooth SCO over HCI transport. + Add support for Android Bluetooth audio quality control. + Add support for Android Bluetooth Low Energy only mode. + +ver 5.18: + Fix issue with identifying LE single mode devices. + Fix issue with L2CAP and RFCOMM peer address lookup. + Add support for handling OBEX authentication procedure. + Add support for Android Bluetooth GATT client interface. + +ver 5.17: + Fix issue with not resetting OBEX SRM setup. + Fix issue with BR/EDR devices and auto-connect list. + Fix issue with bonding complete detection as peripheral. + Fix issue with not updating bearer timestamp of connections. + Fix issue with paired property for multiple bearers. + Add support for Android Bluetooth Handsfree interface. + Add support for Android Bluetooth Wideband speech. + +ver 5.16: + Fix issue with HID over GATT physical location. + Fix issue with HID over GATT unique identifier. + Fix issue with missing paired property notification. + Fix issue with endianess of long term key storage. + Add support for storing signature resolving keys. + Add support for Android Bluetooth AVRCP interface. + +ver 5.15: + Fix issue with LE enabling and background scanning. + Fix issue with HID over GATT input device name. + Fix issue with storage of slave long term keys. + Add support for handling identity resolving keys. + Add support for Android Bluetooth A2DP interface. + Add support for Android Bluetooth audio interface. + +ver 5.14: + Fix issue with marking PS3 controllers as trusted. + Fix issue with authorization of PS3 controllers. + Add support for DualShock 4 controller detection. + Add support for legacy pairing emulation. + Add support for secure simple pairing emulation. + Add support for automated pairing testing. + Add support for RFCOMM protocol testing. + Add support for HCI controller testing. + +ver 5.13: + Fix issue with PS3 controller detection. + Add support for data transfers to L2CAP testing tool. + Add support for delay reporting to AVDTP testing tool. + Add support for Android Bluetooth Core interface. + Add support for Android Bluetooth Socket interface. + Add support for Android Bluetooth HID Host interface. + Add support for Android Bluetooth PAN interface. + +ver 5.12: + Fix issue with missing reply to DisconnectProfile. + Fix issue with icon property and class of device changes. + Fix issue with HID devices when SDP record is not available. + Fix issue with handling auto-pairing of printers. + Fix issue with agent authorization handling. + Add support for PS3 controller setup and pairing. + Add support for LE L2CAP CoC test capabilities. + Add support for AVDTP qualification test cases. + Add support for SMP cryptographic test cases. + +ver 5.11: + Fix issue with connection attempt when not powered. + Fix issue with assigning player to AVRCP target role. + Fix issue with OBEX default cache directory. + Fix issue with SDP search error handling. + Fix issue with processing of SDP records. + Fix issue with HID to HCI switching utility. + Fix issue with mgmt end-to-end testing tool. + Fix issue with L2CAP end-to-end testing tool. + Add support for SMP end-to-end testing tool. + Add support for more Wii controllers. + +ver 5.10: + Fix issue with discoverable timeout handling. + Fix issue with MAP messages and record version. + Fix issue with MAP messages and status events. + Fix issue with MAP messages and relative folders. + Fix issue with MAP messages and type property signals. + Fix issue with transfer size for OBEX GET operations. + Fix issue with AVRCP service class identifier. + Fix issue with AVRCP tracking seeked signal. + Add support for OBEX command line client. + +ver 5.9: + Fix issue with network service and adapter removal. + Fix issue with misleading OBEX error messages. + Fix issue with OBEX transport reference handling. + Fix issue with memory leak with MAP event handler. + Fix issue with missing MAP property changed signal. + Fix issue with message type property values. + Fix issue with empty UUID list for devices. + Fix issue with profile agent cancel method. + Remove dependency on USB library. + +ver 5.8: + Fix issue with missing OBEX session properties. + Fix issue with missing SDP service refresh. + Fix issue with SDP attribute range check. + Fix issue with priority for SDP transactions. + Fix issue with service discovery after pairing. + Fix issue with race condition in service list. + Fix issue with input service state transition. + Fix issue with default authorization for profiles. + Fix issue with AVRCP browsing channel connections. + Add support for AVRCP role agnostic sessions. + +ver 5.7: + Fix issue with missing UUID discovery during pairing. + Fix issue with broken patch for SDP range check handling. + Fix issue with AVRCP usage of UID=0 for paused/stopped. + Add support MAP notification dispatching. + +ver 5.6: + Fix issue with incoming connections without SDP record. + Fix issue with canceling ongoing device connections. + Fix issue with handling failed connection attempts. + Fix issue with pending resume during A2DP open failures. + Fix issue with registering AVRCP unsupported notification. + Fix issue with listing available AVRCP target settings. + Fix issue with missing error for OBEX SetPath commands. + Fix issue with missing OBEX session command queue. + Fix issue with retrieving multiple MAP event reports. + Add support for command line player utility. + +ver 5.5: + Fix issue with race condition between SDP and properties. + Fix issue with handling storage of private device addresses. + Fix issue with NFC out-of-band pairing and power states. + Fix issue with short name during device update handling. + Fix issue with handling AVRCP without A2DP being present. + Add support for handling AVRCP pass-through operations. + Add support for automatically reconnecting HID devices. + Add support for automatically pairing of devices. + +ver 5.4: + Fix issue with invalid memory access and SDP service search. + Add support for available player changed event for controller. + Add support for UIDs changed event for AVRCP controller. + Add support for mandatory AVRCP pass-through operations. + Add support for Message Notification Service (MNS) server. + Add support for agent methods within command line client. + +ver 5.3: + Fix issue with registering invalid profiles. + Fix issue with inconsistent A2DP transport state. + Fix issue with A2DP resume while in configured state. + Fix issue with buffer overflow when processing SDP response. + Fix issue with missing range check for SDP attribute response. + Fix issue with missing validation of SDP data elements. + Fix issue with missing fallback to static hostname. + Fix issue with default adapter assignment. + +ver 5.2: + Fix issue with connection handling for Low Energy. + Fix issue with broken device discovery handling. + Fix issue with invalid memory access within A2DP. + Fix issue with handling empty path name of SetPath. + Fix issue with handling Message Access Profile filters. + Fix issue with handling network service unregistration. + Fix issue with not handling bogus device pairing results. + Fix issue with initial service discovery and profile manager. + Add support for AVRCP volume notifications. + Add support for AVRCP browsing commands. + +ver 5.1: + Fix issue with crash when removing OBEX session. + Fix issue with HID device disconnected from kernel. + Fix issue with buffer overflow when parsing HID SDP record. + Fix issue with SDP_TEXT_STR16 and SDP_URL_STR16 parsing. + Add support for integration with systemd's hostname daemon. + Add support for separate adapter alias property. + Add support for adapter and device modalias properties. + Add support for official BlueZ device information. + Add support for asynchronous management interface handling. + Add tool for testing management interface compliance. + Add tool for testing SDP qualification requirements. + Add tool for testing various EIR and AD data records. + +ver 5.0: + Introduce D-Bus Properties and ObjectManager interfaces. + Add support for generic profile interface. + Add support for global agent interface. + Add support for integrated OBEX daemon. + Add support for integrated hcidump utility. + Add support for Bluetooth tracing and monitor utility. + Add support for Bluetooth command line client utility. + Remove support for Handsfree gateway handling. + Remove support for GStreamer A2DP and SBC elements. + Disable default installation of Bluetooth library. + +ver 4.101: + Fix issue with missing BlueZ service file. + Fix issue with aborting A2DP setup during AVDTP start. + Fix issue with handling of multiple A2DP indication. + Fix issue with handling AVDTP abort with invalid SEID. + Fix issue with rejecting AVDTP abort commands. + Add support for handling AVDTP command collision. + +ver 4.100: + Fix issue with crashing when SCO connection fails. + Fix issue with HFP gateway failing on first GSM connection. + Fix issue with AVRCP and handling of vendor commands. + Fix issue with handling AVRCP subunit info command. + Fix issue with missing capability for AVRCP track reached end. + Fix issue with AVDTP signaling and GStreamer SBC NULL check. + Fix issue with AVDTP Reconfigure Reject message. + Fix issue with incorrect EIR length parsing. + Fix issue with SDP disconnect for HIDSDPDisable. + Fix issue with SDP interoperability with Mac OS X Lion. + Fix issue with reverse SDP discovery with some devices. + Fix issue with discovering state during power off operation. + Add support for AVRCP Volume Changed notifications. + Add support for AVRCP Set Absolute Volume handling. + Add support for display legacy PIN code agent method. + Add support for multiple media transports per endpoint. + Add support for discovering device information characteristics. + Add support for vendor source for Device ID setting. + Add support for immediate alert server. + Add support for link loss server. + + Notes: + This version requires D-Bus 1.4 or later. + This version requires GLib 2.28 or later. + +ver 4.99: + Fix issue with missing retries for BNEP connection setup. + Fix issue with not showing name if first EIR has no details. + Fix issue with running SDP discovery for LE devices. + Add support for GATT using 128-bit Bluetooth UUIDs. + Add support for retrieving key size information. + Add support for storing Long Term Keys. + Add support for Proximity Reporter API. + Add support for KeyboardDisplay IO capability. + Add support for version 1.0 of management API. + Add support for monitoring interface. + +ver 4.98: + Fix issue with adapter list upon initialization failure. + Fix issue with missing legacy property for Low Energy. + Fix issue with missing EIR information handling. + Fix issue with device address type tracking. + Fix issue with alert level characteristic. + Fix issue with headset shutdown handling. + Fix issue with Wiimote address handling. + Add support for advanced l2test options. + Add support for attribute protocol and multiple adapters. + +ver 4.97: + Update support for proximity profile. + Fix issue with SBC audio decoding quality. + Fix multiple issues with HFP support. + Fix multiple issues with A2DP support. + Fix multiple issues with AVDTP support. + Fix multiple issues with AVRCP support. + Add support for AVRCP meta-data transfer. + Add support for Bluetooth based thermometers. + +ver 4.96: + Fix issue with race condition in AVDTP stream start. + Fix issue with global adapter offline switching. + Fix issue with pairing and No Bonding devices. + Add support for Nintendo Wii Remote pairing. + +ver 4.95: + Fix issue with AVCTP replies with invalid PID. + Fix issue with AVRCP and unknown packet types. + Fix issue with AVRCP not using NOT_IMPLEMENTED correctly. + Fix issue with AVDTP discovery if all endpoints are in use. + Fix issue with invalid memory writes and media support. + Fix issue with not removing device alias and unbonding. + Fix issue with device disconnects and offline mode handling. + Add support for setting adapter name based on machine-info. + Add support for systemd service configuration. + +ver 4.94: + Fix issue with invalid read of memory in various modules. + Fix issue with buffer overflow when sending AVDTP commands. + Fix issue with response to vendor dependent AVRCP commands. + Fix issue with headset when not able to reply with ERROR. + Fix issue with crash when creating a device from storage. + Fix issue with handling non UTF-8 devices names. + Add support for improved discovery procedure. + +ver 4.93: + Fix issue with property type and Health Main channel. + Fix issue with crash when removing devices. + Add support for hid2hci and udev integration. + +ver 4.92: + Fix issue with handling of A2DP suspend response. + Fix issue with crashing when acquiring A2DP stream. + Fix issue with missing check for valid SCO before shutdown. + Fix issue with waiting for POLLERR when disconnecting SCO. + Fix issue with disconnect after primary service discovery. + Fix issue with attribute interface registration. + Add support for primary services over BR/EDR. + Add support for flushable packets of A2DP media. + +ver 4.91: + Fix issue with LMP version string and hciconfig. + Fix issue with missing discovery signal when scanning. + Fix issue with wrong state and canceling name resolving. + Fix issue with missing check during adapter initialization. + Fix issue with missing protocol not supported error and A2DP. + Fix issue with crash during driver unregistering and A2DP. + Fix issue with crash when receiving AVDTP close command. + Fix issue with remote SEP handling when A2DP codec changes. + Fix issue with SCO hangup handling and state changes. + Fix issue with security level and MCAP instances. + Fix issue with memory leak and HDP data channels. + Add support for discover characteristics by UUID to gatttool. + Add initial support for Out-of-Band association model. + Add initial support for SIM Access Profile. + +ver 4.90: + Fix issue with setting of global mode property. + Fix issue with handling of RequestSession responses. + Fix issue with TP_BNEP_CTRL_BV_01_C qualification test. + Fix issue with too short AVDTP request timeout. + Add support for SIM Access Profile manager. + Add support for new UUID utility functions. + Add support for attribute server notifications. + Add support for client characteristic configuration. + Update support for interactive GATT utility. + +ver 4.89: + Fix issue with name resolving when discovery is suspended. + Fix issue with parsing flags of advertising report. + Fix issue with SEP handling if interface is disabled. + Fix issue with device object creation on disconnect event. + Fix issue with indicators whenever the driver is initialized. + Fix issue with call indicator when parsing call info reply. + Fix issue with crash and allowed GATT MTU was too large. + Add support for SDP record of Primary GATT services. + Add support for interactive mode for GATT utility. + +ver 4.88: + Fix issue with HID channel reference count handling. + Fix issue with daemon exit on badly formatted AT+VTS. + Fix issue with crash while parsing of endpoint properties. + Fix issue with possible crash on AVDTP Suspend request timeout. + Fix issue with stopping inquiry before adapter is initialized. + Fix issue with creating device object when connection fails. + Fix issue with sending HCIDEVUP when adapter is already up. + Fix issue with handling bonding IO channel closing. + Fix agent cancellation in security mode 3 situations. + Update pairing code to support management interface. + +ver 4.87: + Fix issue with initialization when adapter is already up. + Fix issue with attribute server MTU and incoming connections. + Fix issue with duplicate characteristics after discovery. + +ver 4.86: + Revert wrong fix for SDP PDU size error response. + Fix various memory leaks in A2DP and AVDTP support. + Add Routing property to MediaTransport interface + Add proper tracking mechanism to NREC status. + Add READ_BLOB_REQUEST support to attribute server. + +ver 4.85: + Fix issue with event mask setting for older adapters. + Fix issue with device creation and pairing failures. + Add support for telephony support via oFono. + Add support for characteristic security level. + Update support for service registration. + +ver 4.84: + Fix issue with wrong parameters and device found signals. + Fix issue with leaking EIR data if RSSI does not change. + Fix issue with adapter initialization state. + Fix issue with closing of SDP server sockets. + +ver 4.83: + Fix issue with already connected HFP/HSP endpoints. + Fix missing reply when create device is canceled. + Fix memory leak within the attribute server. + Fix memory leak with unused extended inquiry name. + Fix setting paired state when device->authr is false. + Fix clearing authentication request for renewed keys. + Add support for storing link keys in runtime memory. + Update support for primary service discovery. + +ver 4.82: + Fix crash with mmap of files with multiples of page size. + Fix HFP response and hold (AT+BTRH) command response. + Fix device creation error response when powered off. + Fix device removal when connecting/browsing fails. + Add initial attribute permission implementation. + Add AVDTP SRC stream send buffer size verification. + Add support for setting link policy based on features. + +ver 4.81: + Fix issue with telephony driver initialization. + Fix issue with adapter services list initialization. + Fix crash after simultaneous authentication requests. + Add support for primary service search on device creation. + +ver 4.80: + Fix legacy link key storing for some buggy adapters. + Fix invalid memory access when EIR field length is zero. + Fix adapter initialization to wait for kernel HCI commands. + Fix initialization of adapters which are already up. + Fix possible race condition when initializing adapters. + Fix possible crashes when attempting to connect AVDTP. + Fix not aborting sink stream configuration on disconnect. + Fix not indicating disconnected state when connecting to AVDTP. + Fix not dropping AVDTP session when canceling stream setup. + Fix AVDTP abort not being send when the state is idle. + Fix regression with Low Energy and interleave discovery. + Add a new configuration option to disable Low Energy support. + Add iwmmxt optimization for SBC for ARM PXA series CPUs. + Update support for GATT Primary Service Discovery. + Update MCAP and HDP support. + +ver 4.79: + Fix issue with adapter initialization race condition. + Update new Bluetooth Management interface support. + +ver 4.78: + Fix various issues with AVDTP timer handling. + Fix various issues with handling of mode changes. + Fix issue with audio disconnect watch in connecting state. + Fix issue with handling call waiting indicators in telephony. + Fix issue with handling UUID parameter and RegisterEndpoint. + Add initial support for Bluetooth Management interface. + Add support for Application property to HealthChannel. + +ver 4.77: + Fix issue with device name and accessing already freed memory. + Fix issue with handling CHLD=0 command for handsfree. + Fix issue with manager properties and no adapters. + Fix issue with properties and broken service records. + Fix issue with A2DP playback and sample rate changes. + Update MCAP and HDP support. + +ver 4.76: + Fix issue in telephony driver with hanging up held call. + Fix issue in telephony driver with notifications when on hold. + Fix issue with blocking on setconf confirmation callback. + Fix issue with not always signaling new streams as sinks. + Fix issue with errors in case of endpoint request timeout. + Fix issue with HFP/HSP microphone and speaker gain values. + Add source if the device attempt to configure local sink stream. + Add PSM option for GATT/ATT over BR/EDR on gatttool. + Add support for GATT/ATT Attribute Write Request. + Update MCAP and HDP support. + +ver 4.75: + Fix use of uninitialized variable on legacy pairing. + Fix mismatch of attribute protocol opcode. + +ver 4.74: + Fix regression for Legacy Pairing. + Fix wrong PSM value for attribute protocol. + Fix issue with RSSI field in advertising reports. + Add support for Add BR/EDR and LE interleaved discovery. + Add support for GATT write characteristic value option. + Add support for specifying download address for AR300x. + +ver 4.73: + Fix problem with EIR data when setting the name. + Fix reading local name from command complete event. + Fix registering local endpoints with disabled socket interface. + Add support for more HCI operations using ops infrastructure. + Add support for GATT characteristic hierarchy. + Add support for GATT indications. + +ver 4.72: + Fix memory leak while connecting BTIO channels. + Fix crash with GStreamer plugin if SBC is not supported. + Fix issue with GATT server stop sending notifications. + Fix issue with GATT and dealing with the minimum MTU size. + Fix issue with file descriptor leak in GATT client. + Add support for UUID 128-bit handling in attribute client. + Add support for encoders/decoders for MTU Exchange. + Add support for the MTU Exchange procedure to the server. + Add support for a per channel MTU to the ATT server. + Add support for Characteristic interface. + Add support for new Media API and framework. + Add initial support for HDP plugin. + +ver 4.71: + Fix compilation when SBC support in not enabled. + Fix crash with RequestSession and application disconnects. + Fix memory leak and possible crash when removing audio device. + Fix issue with closing stream of locked sep when reconfiguring. + Fix issue where discovery could interfere with bonding. + Fix issue with Connected status when PS3 BD remote connects. + Fix issue with lifetime of fake input devices. + Add support for compile time option of oui.txt path. + Add support for printing IEEE1284 device ID for CUPS. + Add plugin for setting adapter class via DMI. + Add more features for attribute protocol and profile. + Add initial support for MCAP. + +ver 4.70: + Fix incoming call indication handling when in WAITING state. + Fix various SDP related qualification test case issues. + Fix logic to write EIR when SDP records are changed. + Fix UTF-8 validity check for remote names in EIR. + Add support for UUID-128 extended inquiry response. + Add service UUIDs from EIR to the DeviceFound signal. + Add fast connectable feature for Handsfree profile. + Add HCI command and event definitions for AMP support. + Add firmware download support for Qualcommh devices. + Add host level support for Atheros AR300x device. + Add initial support of ATT and GATT for basic rate. + +ver 4.69: + Fix issue with calling g_option_context_free() twice. + Fix inconsistencies with initial LE commands and events. + Add support for telephony ClearLastNumber method. + Add support for network server interface. + +ver 4.68: + Fix initialization of adapters in RAW mode. + Fix signal strength for HFP in Maemo's telephony support. + Add support for following the radio state via Maemo's MCE. + Add initial set of LE commands and events definitions. + Add mode option for L2CAP sockets to the BtIO API. + +ver 4.67: + Fix issue with authentication reply when bonding already completed. + Fix issue with not canceling authentication when bonding fails. + Fix issue with changed combination keys and temporary storage. + Fix issue with sdp_get_supp_feat library function. + Fix issue with missing unblock on device removal. + Fix issue with not waiting for mode change completion. + Add ARMv6 optimized version of analysis filter for SBC encoder. + +ver 4.66: + Fix regression with full debug enabling via SIGUSR2. + Fix redundant speaker/microphone gains being sent. + Fix not emitting PropertyChanged for SpeakerGain/MicrophoneGain. + Fix issue with storage usage when a record is not found in memory. + Fix issue with DiscoverServices not retrieving any records. + Fix audio profile disconnection order to match whitepaper. + Fix auto-accept confirmation when local agent has NoInputNoOutput. + Fix remote just-works SSP when MITM protection is required. + Fix performing dedicated bonding without MITM requirement. + Add support for storing debug link keys in runtime memory. + +ver 4.65: + Fix issues with general bonding being default setting now. + Fix driver removal upon device removal. + Add new "Blocked" property to device objects. + Add hciconfig support for blacklisting. + Add support for dynamic debug feature. + +ver 4.64: + Fix invalid memory access in headset_get_nrec function. + Fix issue with disconnect event on higher protocol layers. + Fix issue with list parsing in sdp_set_supp_features function. + Fix device object reference counting for SDP browse requests. + Add missing memory checks whenever memory is allocated for SDP. + Add support for exporting local services via D-Bus. + Add more L2CAP Enhanced Retransmission test options. + +ver 4.63: + Fix avdtp_abort not canceling pending requests. + Fix stale connection when abort gets rejected. + +ver 4.62: + Fix accidental symbol breakage with inquiry transmit power. + Fix using invalid data from previous headset connection. + Fix double free on AVDTP Abort response. + Fix possible crash while verifying AVDTP version. + Fix missing inuse flag when AVDTP stream is configured. + Add support for Bluetooth controller types. + +ver 4.61: + Fix issues with Read Inquiry Response Transmit Power Level. + Fix possible invalid read when removing a temporary device. + Fix mode restoration when remember_powered is false. + Fix conference call releasing in telephony-maemo. + Fix segmentation fault with authorization during headset disconnects. + Add support for handling unanswered AVDTP request on disconnect. + Add support for handling Inquiry Response Transmit Power Level. + Add support for caching of remote host features. + Add preliminary voice dialing support for HSP. + +ver 4.60: + Fix voice mailbox number reading from SIM. + Fix some races with D-Bus mainloop integration. + Add helpers for D-Bus signal watches. + +ver 4.59: + Add values for Bluetooth 4.0 specification. + Add SDP functions for HDP support. + Add test scripts for input and audio. + Fix missing close on BtIO create_io function. + Fix sending incorrect AVDTP commands after timeout occurs. + Fix timer removal when device disconnects unexpectedly. + Fix Extended Inquiry Response record for Device ID. + +ver 4.58: + Fix crash when adapter agent exists during authentication. + Fix CK-20W quirks for play and pause events. + +ver 4.57: + Fix unloading of drivers for uninitialized adapters. + Fix debug message to use requested and not opened SEID. + Fix codec selection for GStreamer plugin. + Fix deleting of SDP records during service updates. + Fix deleting of SDP records when a device is removed. + Fix handling when the SDP record is modified on remote device. + Fix potential buffer overflow by using snprintf instead of sprintf. + Fix const declarations for some storage function parameters. + +ver 4.56: + Add missing values from Bluetooth 3.0 specification. + Add proper tracking of device paired status. + Fix tracking of devices without permanently stored link key. + Fix issue with link key removal after connection failures. + Fix legacy pairing information based on remote host features. + Fix off-by-one issue with AVDTP capability parsing. + Fix AVRCP, AVCTP, AVDTP, A2DP and HFP version numbers. + Fix agent canceling before calling agent_destroy. + Fix service record parsing with an empty UUID list. + Fix various SDP related memory leaks. + +ver 4.55: + Add support for POSIX capabilities dropping. + Add special quirk for the Nokia CK-20W car kit. + Fix error code handling for AVDTP SetConfiguration response. + Fix updating out of range list when RSSI hasn't changed. + Fix various memory leaks and unnecessary error checks. + +ver 4.54: + Add introspection interface to output of introspection calls. + Fix stream handling when media transport disconnects prematurely. + Fix command timeout handling when there's no stream. + Fix headset_suspend_stream behavior for invalid states + Fix issue with AVDTP ABORTING state transition. + Fix issue with AVDTP suspend while closing. + +ver 4.53: + Fix issue with telephony connection state notifications. + Fix AVDTP stream leak for invalid media transport config. + Fix audio connection authorization handling with timeouts. + Fix race condition in authorizing audio connections. + Fix device authorized setting for AVRCP-only connections. + Fix duplicate attempts from device to connect signal channel. + +ver 4.52: + Add AVCTP support to test utility. + Fix AVDTP Abort when transport closes before response. + Fix authorization when the audio profiles are slow to connect. + Fix potential AVDTP reference leaks. + +ver 4.51: + Add utility for basic AVDTP testing. + Add support for configuring L2CAP FCS option. + Fix discovery mode for CUPS 1.4.x and later. + Fix global state tracking of audio service. + Fix last issues with the new build system. + +ver 4.50: + Fix issue with missing manual pages in distribution. + Fix issue with the configuration and state directories. + Fix issue with creating include directory. + Fix dependencies of include file generation. + +ver 4.49: + Add simple test program for basic GAP testing. + Add support for confirmation requests to agent example. + Add support for full non-recursive build. + Add five millisecond delay for Simple Pairing auto-accept. + Fix Class of Device setting when InitiallyPowered=false. + +ver 4.48: + Add library function for comparing UUID values. + Add support for creating all plugins as builtins. + Add support for async handling of service class changes. + Add support for source interface to audio IPC. + Fix device name settings when device is off or down. + Fix issue with enabled SCO server when not necessary. + Fix missing D-Bus access policy for CUPS backend. + Fix discovery results of CUPS backend. + Fix initialization handling of Maemo telephony. + +ver 4.47: + Add support for RFKILL unblock handling. + Add support for serial proxy configurations. + Add support for caching service class updates. + Fix issues with updating SDP service records. + Fix usage of limited discoverable mode. + Remove deprecated methods and signals for AudioSource. + +ver 4.46: + Add support for A2DP sink role. + Fix clearing svc_cache before the adapter is up. + Fix various pointer after free usages. + Fix various memory leaks. + +ver 4.45: + Fix UDEV_DATADIR fallback if pkg-config fails. + Fix adapter cleanup and setup prototypes. + Fix double-free with out-of-range devices. + Fix inband ring setting to be per-headset. + Fix handling of Maemo CSD startup. + +ver 4.44: + Add some missing manual pages. + Fix missing number prefix when installing udev rules. + Fix program prefix used in Bluetooth udev rules. + Fix three-way calling indicator order. + Fix downgrade/upgrade of callheld indicator. + Fix +CIEV sending when indicator value changes. + Fix signal handling for Maemo telephony driver. + Fix parsing issues with messages from Maemo CSD. + Fix issue with duplicate active calls. + +ver 4.43: + Add support for udev based on-demand startup. + Fix verbose error reporting of CUPS backend. + Fix various string length issues. + Fix issues with Maemo telephony driver. + Fix another device setup and temporary flag issue. + Fix and update example agent implementation. + +ver 4.42: + Add TI WL1271 to Texas Instruments chip list. + Add special udev mode to bluetoothd. + Fix regression when there is no agent registered. + Fix error return when bonding socket hang up. + Fix SCO server socket for HFP handsfree role. + Fix shutdown on SCO socket before closing. + Fix shutdown on A2DP audio stream channel before closing. + Fix issue with asserting on AVDTP reference count bugs. + Fix authorization denied issue with certain headsets. + Fix AVRCP UNITINFO and SUBUNIT INFO responses. + Fix discovery cancel issues in case SDP discovery fails. + +ver 4.41: + Fix pairing even if the ACL gets dropped before successful SDP. + Fix regression which caused device to be removed after pairing. + Fix HSP record fetching when remote device doesn't support it. + Fix SDP discovery canceling when clearing hs->pending. + Fix headset never connecting on the first attempt. + Fix headset state tracking if bt_search_service() fails. + Fix maximum headset connection count check. + Fix AVDTP Discover timeout handling. + Fix also UI_SET_KEYBIT for the new pause and play key codes. + +ver 4.40: + Add telephony driver for oFono telephony stack. + Add support for Dell specific HID proxy switching. + Add support for running hid2hci from udev. + Add mapping for AVRCP Play and Pause to dedicated key codes. + Fix AVRCP keycodes to better match existing X keymap support. + Fix various quoting issues within telephony support. + Fix memory allocation issue when generating PDUs for SDP. + Fix race condition on device removal. + Fix non-cancelable issue with CreateDevice method. + Fix non-working CancelDiscovery method call. + +ver 4.39: + Add workaround for dealing with unknown inquiry complete. + Fix discovering when using software scheduler. + Fix wrong NoInputNoOutput IO capability string. + Fix race condition with agent during pairing. + Fix agent cancellation for security mode 3 acceptor failure. + Fix temporary flag removal when device creation fails. + Fix hciattach to use ppoll instead of poll. + Fix service class update when adapter is down. + Fix service classes race condition during startup. + Fix release of audio client before freeing the device. + +ver 4.38: + Add support for builtin plugins. + Add framework for adapter operations. + Add constants for Enhanced Retransmission modes. + Fix HCI socket leak in device_remove_bonding. + Fix various format string issues. + Fix crashes with various free functions. + Fix issues with Headset and A2DP drivers to load again. + Fix sending AVRCP button released passthrough messages + Fix bug which prevent input devices to work after restart. + Fix issue with interpretation of UUID-128 as channel. + +ver 4.37: + Add version value for Bluetooth 3.0 devices. + Add additional L2CAP extended feature mask bits. + Add support for loading plugins in priority order. + Add support for more detailed usage of disconnect watches. + Add support for AVRCP volume control. + Add saturated clipping of SBC decoder output to 16-bit. + Fix potentially infinite recursion of adapter_up. + Fix SCO handling in the case of an incoming call. + Fix input service to use confirm callback. + Fix cleanup of temporary device entries from storage. + +ver 4.36: + Add proper tracking of AVCTP connect attempts. + Add support to channel pattern in Serial interface. + Fix A2DP sink crash if removing device while connecting. + Fix error handling if HFP indicators aren't initialized. + Fix segfault while handling an incoming SCO connection. + Fix Serial.Disconnect to abort connection attempt. + +ver 4.35: + Add support for Handsfree profile headset role. + Add additional checks for open SEIDs from clients. + Fix device removal while audio IPC client is connected. + Fix device removal when an authorization request is pending. + Fix incoming AVDTP connect while authorization in progress. + Fix disconnection timers for audio support. + Fix various potential NULL pointer deferences. + Fix callheld indicator value for multiple calls. + Fix voice number type usage. + Fix GDBus watch handling. + +ver 4.34: + Add support for version checks of plugins. + Add support for class property on adapter interface. + Add support for second SDP attempt after connection reset. + Add support for more detailed audio states. + Add support for HFP+A2DP auto connection feature. + Add support for new and improved audio IPC. + Add program for testing audio IPC interface. + Fix various AVDTP qualification related issues. + Fix broken SDP AttributeIdList parsing. + Fix invalid memory access of SDP URL handling. + Fix local class of device race conditions. + Fix issue with periodic inquiry on startup. + Fix missing temporary devices in some situations. + Fix SBC alignment issue for encoding with four subbands. + +ver 4.33: + Add Paired property to the DeviceFound signals. + Add support for Headset profile 1.2 version. + Fix broken network configuration when IPv6 is disabled. + Fix network regression that caused disconnection. + Fix SDP truncation of strings with NULL values. + Fix service discovery handling of CUPS helper. + +ver 4.32: + Fix broken SDP record handling. + Fix SDP data buffer parsing. + Fix more SDP memory leaks. + Fix read scan enable calls. + Fix A2DP stream handling. + +ver 4.31: + Add support for new BtIO helper library. + Fix AVDTP session close issue. + Fix SDP memory leaks. + Fix various uninitialized memory issues. + Fix duplicate signal emissions. + Fix property changes request handling. + Fix class of device storage handling. + +ver 4.30: + Add CID field to L2CAP socket address structure. + Fix reset of authentication requirements after bonding. + Fix storing of link keys when using dedicated bonding. + Fix storing of pre-Bluetooth 2.1 link keys. + Fix resetting trust settings on every reboot. + Fix handling of local name changes. + Fix memory leaks in hciconfig and hcitool + +ver 4.29: + Use AVRCP version 1.0 for now. + Decrease AVDTP idle timeout to one second. + Delay AVRCP connection when remote device connects A2DP. + Add workaround for AVDTP stream setup with broken headsets. + Add missing three-way calling feature bit for Handsfree. + Fix handsfree callheld indicator updating. + Fix parsing of all AT commands within the buffer. + Fix authentication replies when disconnected. + Fix handling of debug combination keys. + Fix handling of changed combination keys. + Fix handling of link keys when using no bonding. + Fix handling of invalid/unknown authentication requirements. + Fix closing of L2CAP raw socket used for dedicated bonding. + +ver 4.28: + Add AVDTP signal fragmentation support. + Add more SBC performance optimizations. + Add more SBC audio quality improvements. + Use native byte order for audio plugins. + Set the adapter alias only after checking the EIR data. + Fix auto-disconnect issue with explicit A2DP connections. + Fix invalid memory access of ALSA plugin. + Fix compilation with -Wsign-compare. + +ver 4.27: + Add more SBC optimization (MMX and ARM NEON). + Add BT_SECURITY and BT_DEFER_SETUP definitions. + Add support for deferred connection setup. + Add support for fragmentation of data packets. + Add option to trigger dedicated bonding. + Follow MITM requirements from remote device. + Require MITM for dedicated bonding if capabilities allow it. + Fix IO capabilities for non-pairing and pairing cases. + Fix no-bonding connections in non-bondable mode. + Fix new pairing detection with SSP. + Fix bonding with pre-2.1 devices and newer kernels. + Fix LIAC setting while toggling Pairable property. + Fix device creation for incoming security mode 3 connects. + Fix crash within A2DP with bogus pointer. + Fix issue with sdp_copy_record() function. + Fix crash with extract_des() if sdp_uuid_extract() fails. + +ver 4.26: + Use of constant shift in SBC quantization code. + Add possibility to analyze 4 blocks at once in encoder. + Fix correct handling of frame sizes in the encoder. + Fix for big endian problems in SBC codec. + Fix audio client socket to always be non-blocking. + Update telephony support for Maemo. + +ver 4.25: + Fix receiving data over the audio control socket. + Fix subbands selection for joint-stereo in SBC encoder. + Add new SBC analysis filter function. + +ver 4.24: + Fix signal emissions when removing adapters. + Fix missing adapter signals on exit. + Add support for bringing adapters down on exit. + Add support for RememberPowered option. + Add support for verbose compiler warnings. + Add more options to SBC encoder. + +ver 4.23: + Update audio IPC for better codec handling. + Fix bitstream optimization for SBC encoder. + Fix length header values of IPC messages. + Fix multiple coding style violations. + Fix FindDevice to handle temporary devices. + Add configuration option for DeviceID. + Add support for InitiallyPowered option. + Add missing signals for manager properties. + Add telephony support for Maemo. + +ver 4.22: + Add deny statements to D-Bus access policy. + Add support for LegacyPairing property. + Add support for global properties. + Add more commands to telephony testing script. + Add sender checks for serial and network interfaces. + Remove deprecated methods and signals from input interface. + Remove deprecated methods and signals from network interface. + Remove OffMode option and always use device down. + +ver 4.21: + Fix adapter initialization logic. + Fix adapter setup and start security manager early. + Fix usage issue with first_init variable. + +ver 4.20: + Cleanup session handling. + Cleanup mode setting handling. + Fix issue with concurrent audio clients. + Fix issue with HFP/HSP suspending. + Fix AT result code syntax handling. + Add Handsfree support for AT+NREC. + Add PairableTimeout adapter property. + +ver 4.19: + Fix installation of manual pages for old daemons. + Fix D-Bus signal emmissions for CreateDevice. + Fix issues with UUID probing. + Fix +BSRF syntax issue. + Add Pairable adapter property. + Add sdp_copy_record() library function. + +ver 4.18: + Fix release before close issue with RFCOMM TTYs. + Fix Connected property on input interface. + Fix DeviceFound signals during initial name resolving. + Fix service discovery handling. + Fix duplicate UUID detection. + Fix SBC gain mismatch and decoding handling. + Add more options to SBC encoder and decoder. + Add special any adapter object for service interface. + Add variable prefix to adapter and device object paths. + +ver 4.17: + Fix SBC encoder not writing last frame. + Fix missing timer for A2DP suspend. + Add more supported devices to hid2hci utility. + Add additional functionality to Handsfree support. + +ver 4.16: + Fix wrong parameter usage of watch callbacks. + Fix parameters for callback upon path removal. + Fix unloading of adapter drivers. + +ver 4.15: + Fix various A2DP state machine issues. + Fix some issues with the Handsfree error reporting. + Fix format string warnings with recent GCC versions. + Remove dependency on GModule. + +ver 4.14: + Fix types of property arrays. + Fix potential crash with input devices. + Fix PS3 BD remote input event generation. + Allow dynamic adapter driver registration. + Update udev rules. + +ver 4.13: + Fix service discovery and UUID handling. + Fix bonding issues with Simple Pairing. + Fix file descriptor misuse of SCO connections. + Fix various memory leaks in the device handling. + Fix AVCTP disconnect handling. + Fix GStreamer modes for MP3 encoding. + Add operator selection to Handsfree support. + +ver 4.12: + Fix crash with missing icon value. + Fix error checks of HAL plugin. + Fix SCO server socket cleanup on exit. + Fix memory leaks from DBusPendingCall. + Fix handling of pending authorization requests. + Fix missing protocol UUIDs in record pattern. + +ver 4.11: + Change SCO server socket into a generic one. + Add test script for dummy telephony plugin. + Fix uninitialized reply of multiple GetProperties methods. + +ver 4.10: + Fix memory leaks with HAL messages. + Add more advanced handsfree features. + Add properties to audio, input and network interfaces. + Stop device discovery timer on device removal. + +ver 4.9: + Fix signals for Powered and Discoverable properties. + Fix handling of Alias and Icon properties. + Fix duplicate entries for service UUIDs. + +ver 4.8: + Fix retrieving of formfactor value. + Fix retrieving of local and remote extended features. + Fix potential NULL pointer dereference during pairing. + Fix crash with browsing due to a remotely initated pairing. + +ver 4.7: + Fix pairing and service discovery logic. + Fix crashes during suspend and resume. + Fix race condition within devdown mode. + Add RequestSession and ReleaseSession methods. + Add Powered and Discoverable properties. + Add Devices property and deprecate ListDevices. + Add workaround for a broken carkit from Nokia. + +ver 4.6: + Fix Device ID record handling. + Fix service browsing and storage. + Fix authentication and encryption for input devices. + Fix adapter name initialization. + +ver 4.5: + Fix initialization issue with new adapters. + Send HID authentication request without blocking. + Hide the verbose SDP debug behind SDP_DEBUG. + Add extra UUIDs for service discovery. + Add SCO server socket listener. + Add authorization support to service plugin. + +ver 4.4: + Add temporary fix for the CUPS compile issue. + Add service-api.txt to distribution. + Mention the variable prefix of an object path + +ver 4.3: + Add dummy driver for telephony support. + Add support for discovery sessions. + Add service plugin for external services. + Various cleanups. + +ver 4.2: + Avoid memory copies in A2DP write routine. + Fix broken logic with Simple Pairing check and old kernels. + Allow non-bondable and outgoing SDP without agent. + Only remove the bonding for non-temporary devices. + Cleanup various unnecessary includes. + Make more unexported functions static. + Add basic infrastructure for gtk-doc support. + +ver 4.1: + Add 30 seconds timeout to BNEP connection setup phase. + Avoid memory copies in A2DP write routine for ALSA. + Make sure to include compat/sdp.h in the distribution. + +ver 4.0: + Initial public release. + +ver 3.36: + Add init routines for TI BRF chips. + Add extra attributes to the serial port record. + Add example record for headset audio gateway record. + Use Handsfree version 0x0105 for the gateway role. + Fix SDP record registration with specific record handles. + Fix BCSP sent/receive handling. + Fix various includes for cross-compilation. + Allow link mode settings for outgoing connections. + Allow bonding during periodic inquiry. + +ver 3.35: + Add two additional company identifiers. + Add UUID-128 support for service discovery. + Fix usage of friendly names for service discovery. + Fix authorization when experiemental is disabled. + Fix uninitialized variable in passkey request handling. + Enable output of timestamps for l2test and rctest. + +ver 3.34: + Replace various SDP functions with safe versions. + Add additional length validation for incoming SDP packets. + Use safe function versions for SDP client handling. + Fix issue with RemoveDevice during discovery procedure. + Fix collect for non-persistent service records. + +ver 3.33: + Add functions for reading and writing the link policy settings. + Add definition for authentication requirements. + Add support for handling Simple Pairing. + Add Simple Pairing support to Agent interface. + Add ReleaseMode method to Adapter interface. + Add DiscoverServices method to Device interface. + Remove obsolete code and cleanup the repository. + Move over to use the libgdbus API. + Enable PIE by default if supported. + +ver 3.32: + Add OCF constants for synchronous flow control enabling. + Add support for switching HID proxy devices from Dell. + Add more Bluetooth client/server helper functions. + Add support for input service idle timeout option. + Fix BNEP reconnection handling. + Fix return value for snd_pcm_hw_params() calls. + Use upper-case addresses for object paths. + Remove HAL support helpers. + Remove inotify support. + Remove service daemon activation handling. + Remove uneeded D-Bus API extension. + +ver 3.31: + Create device object for all pairing cases. + Convert authorization to internal function calls. + Add initial support for Headset Audio Gateway role. + Add generic Bluetooth helper functions for GLib. + Fix endiannes handling of connection handles. + Don't optimize when debug is enabled. + +ver 3.30: + Convert audio service into a plugin. + Convert input service into a plugin. + Convert serial service into a plugin. + Convert network service into a plugin. + Emit old device signals when a property is changed. + Fix missing DiscoverDevices and CancelDiscovery methods. + Add another company identifier. + Add basic support for Bluetooth sessions. + Add avinfo utility for AVDTP/A2DP classification. + Remove build option for deprecated sdpd binary. + +ver 3.29: + Introduce new D-Bus based API. + Add more SBC optimizations. + Add support for PS3 remote devices. + Fix alignment trap in SDP server. + Fix memory leak in sdp_get_uuidseq_attr function. + +ver 3.28: + Add support for MCAP UUIDs. + Add support for role switch for audio service. + Add disconnect timer for audio service. + Add disconnect detection to ALSA plugin. + Add more SBC optimizations. + Fix alignment issue of SDP server. + Remove support for SDP parsing via expat. + +ver 3.27: + Update uinput.h with extra key definitions. + Add support for input connect/disconnect callbacks. + Add ifdefs around some baud rate definitions. + Add another company identifier. + Add proper HFP service level connection handling. + Add basic headset automatic disconnect support. + Add support for new SBC API. + Fix SBC decoder noise at high bitpools. + Use 32-bit multipliers for further SBC optimization. + Check for RFCOMM connection state in SCO connect callback. + Make use of parameters selected in ALSA plugin. + +ver 3.26: + Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX. + Improve handling of different audio transports. + Enable services by default and keep old daemons disabled. + +ver 3.25: + Add limited support for Handsfree profile. + Add limited support for MPEG12/MP3 codec. + Add basic support for UNITINFO and SUBUNITINFO. + Add more SBC optimizations. + Fix external service (un)registration. + Allow GetInfo and GetAddress to fail. + +ver 3.24: + Add definitions for MDP. + Add TCP connection support for serial proxy. + Add fix for Logitech HID proxy switching. + Add missing macros, MIN, MAX, ABS and CLAMP. + Add more SBC encoder optimizations. + Add initial mechanism to handle headset commands. + Fix connecting to handsfree profile headsets. + Use proper function for checking signal name. + +ver 3.23: + Fix remote name request handling bug. + Fix key search function to honor the mmap area size. + Fix Avahi integration of network service. + Add new plugin communication for audio service. + Enable basic AVRCP support by default. + More optimizations to the SBC library. + Create common error definitions. + +ver 3.22: + Add missing include file from audio service. + Add SBC conformance test utility. + Add basic uinput support for AVRCP. + Fix L2CAP socket leak in audio service. + Fix buffer usage in GStreamer plugin. + Fix remote name request event handling. + +ver 3.21: + Add constant for Bluetooth socket options level. + Add initial AVRCP support. + Add A2DP sink support to GStreamer plugin. + Fix interoperability with A2DP suspend. + Fix sign error in 8-subband encoder. + Fix handling of service classes length size. + Store Extended Inquiry Response data information. + Publish device id information through EIR. + Support higher baud rates for Ericcson based chips. + +ver 3.20: + Fix GStreamer plugin file type detection. + Fix potential infinite loop in inotify support. + Fix D-Bus signatures for dict handling. + Fix issues with service activation. + Fix SDP failure handling of audio service. + Fix various memory leaks in input service. + Add secure device creation method to input service. + Add service information methods to serial service. + Add config file support to network service. + Add scripting capability to network service. + Add special on-mode handling. + Add optimization for SBC encoder. + Add tweaks for D-Bus 1.1.x libraries. + Add support for inquiry transmit power level. + +ver 3.19: + Limit range of bitpool announced while in ACP side. + Use poll instead of usleep to wait for worker thread. + Use default event mask from the specification. + Add L2CAP mode constants. + Add HID proxy support for Logitech diNovo Edge dongle. + Add refresh option to re-request device names. + Show correct connection link type. + +ver 3.18: + Don't allocate memory for the Bluetooth base UUID. + Implement proper locking for headsets. + Fix various A2DP SEP locking issues. + Fix and cleanup audio stream handling. + Fix stream starting if suspend request is pending. + Fix A2DP and AVDTP endianess problems. + Add network timeout and retransmission support. + Add more detailed decoding of EIR elements. + +ver 3.17: + Fix supported commands bit calculation. + Fix crashes in audio and network services. + Check PAN source and destination roles. + Only export the needed symbols for the plugins. + +ver 3.16: + Update company identifier list. + Add support for headsets with SCO audio over HCI. + Add support for auto-create through ALSA plugin. + Add support for ALSA plugin parameters. + Add GStreamer plugin with SBC decoder and encoder. + Fix network service NAP, GN and PANU servers. + Set EIR information from SDP database. + +ver 3.15: + Add A2DP support to the audio service. + Add proxy support to the serial service. + Extract main service class for later use. + Set service classes value from SDP database. + +ver 3.14: + Add missing signals for the adapter interface. + Add definitions and functions for Simple Pairing. + Add basic commands for Simple Pairing. + Add correct Simple Pairing and EIR interaction. + Add missing properties for remote information. + Add EPoX endian quirk to the input service. + Fix HID descriptor import and storage functions. + Fix handling of adapters in raw mode. + Fix remote device listing methods. + +ver 3.13: + Fix some issues with the headset support. + Fix concurrent pending connection attempts. + Fix usage of devname instead of netdev. + Add identifier for Nokia SyncML records. + Add command for reading the CSR chip revision. + Add generic CSR radio test support. + Update HCI command table. + +ver 3.12: + Add missing HCI command text descriptions + Add missing HCI commands structures. + Add missing HCI event structures. + Add common bachk() function. + Add support for limited discovery mode. + Add support for setting of event mask. + Add GetRemoteServiceIdentifiers method. + Add skeleton for local D-Bus server. + Add headset gain control methods. + Fix various headset implementation issues. + Fix various serial port service issues. + Fix various input service issues. + Let CUPS plugin discover printers in range. + Improve the BCM2035 UART init routine. + Ignore connection events for non-ACL links. + +ver 3.11: + Update API documentation. + Minimize SDP root records and browse groups. + Use same decoder for text and URL strings. + Fix URL data size handling. + Fix SDP pattern extraction for XML. + Fix network connection persistent state. + Add network connection helper methods. + Add initial version of serial port support. + Add class of device tracking. + +ver 3.10.1: + Add option to disable installation of manual pages. + Fix input service encryption setup. + Fix serial service methods. + Fix network service connection handling. + Provide a simple init script. + +ver 3.10: + Add initial version of network service. + Add initial version of serial service. + Add initial version of input service. + Add initial version of audio service. + Add authorization framework. + Add integer based SBC library. + Add version code for Bluetooth 2.1 specification. + Add ESCO_LINK connection type constant. + Export sdp_uuid32_to_uuid128() function. + +ver 3.9: + Add RemoteDeviceDisconnectRequested signal. + Add updated service framework. + Add embedded GLib library. + Add support for using system GLib library. + Create internal SDP server library. + +ver 3.8: + Sort discovered devices list based on their RSSI. + Send DiscoverableTimeoutChanged signal. + Fix local and remote name validity checking. + Add ListRemoteDevices and ListRecentRemoteDevices methods. + Add basic integration of confirmation concept. + Add support for service record description via XML. + Add support for external commands to the RFCOMM utility. + Add experimental service and authorization API. + Add functions for registering binary records. + +ver 3.7: + Fix class of device handling. + Fix error replies with pairing and security mode 3. + Fix disconnect method for RFCOMM connections. + Add match pattern for service searches. + Add support for prioritized watches. + Add additional PDU length checks. + Fix CSRC value for partial responses. + +ver 3.6.1: + Fix IO channel race conditions. + Fix pairing issues on big endian systems. + Fix pairing issues with page timeout errors. + Fix pairing state for security mode 3 requests. + Switch to user as default security manager mode. + +ver 3.6: + Update D-Bus based RFCOMM interface support. + Use L2CAP raw sockets for HCI connection creation. + Add periodic discovery support to the D-Bus interface. + Add initial support for device names via EIR. + Add proper UTF-8 validation of device names. + Add support for the J-Three keyboard. + Fix issues with the asynchronous API for SDP. + +ver 3.5: + Fix and cleanup watch functionality. + Add support for periodic inquiry mode. + Add support for asynchronous SDP requests. + Add more request owner tracking. + Add asynchronous API for SDP. + Document pageto and discovto options. + +ver 3.4: + Improve error reporting for failed HCI commands. + Improve handling of CancelBonding. + Fixed bonding reply message when disconnected. + Fix UUID128 string lookup handling. + Fix malloc() versus bt_malloc() usage. + +ver 3.3: + Don't change inquiry mode for Bluetooth 1.1 adapters. + Add udev rules for Bluetooth serial PCMCIA cards. + Add Cancel and Release methods for passkey agents. + Add GetRemoteClass method. + Convert to using ppoll() and pselect(). + Initialize allocated memory to zero. + Remove bcm203x firmware loader. + Remove kernel specific timeouts. + Add additional private data field for SDP sessions. + Add host controller to host flow control defines. + Add host number of completed packets defines. + Initialize various memory to zero before usage. + +ver 3.2: + Only check for the low-level D-Bus library. + Update possible device minor classes. + Fix timeout for pending reply. + Add more Inquiry with RSSI quirks. + Sleep only 100 msecs for device detection. + Don't send BondingCreated on link key renewal. + Allow storing of all UTF-8 remote device names. + Create storage filenames with a generic function. + Fix handling of SDP strings. + Add adapter type for SDIO cards. + Add features bit for link supervision timeout. + +ver 3.1: + Add missing placeholders for feature bits. + Fix handling of raw mode devices. + Fix busy loop in UUID extraction routine. + Remove inquiry mode setting. + Remove auth and encrypt settings. + +ver 3.0: + Implement the new BlueZ D-Bus API. + Fix broken behavior with EVT_CMD_STATUS. + Add features bit for pause encryption. + Add additional EIR error code. + Add more company identifiers. + Add another Phonebook Access identifier. + Update sniff subrating data structures. + +ver 2.25: + Use %jx instead of %llx for uint64_t and int64_t. + Allow null-terminated text strings. + Add UUID for N-Gage games. + Add UUID for Apple Macintosh Attributes. + Add Apple attributes and iSync records. + Add definitions for Apple Agent. + Add support for the Handsfree Audio Gateway service. + Add support for choosing a specific record handle. + Add support for dialup/telephone connections. + Add definitions for Apple Agent. + Add support for record handle on service registration. + +ver 2.24: + Fix display of SDP text and data strings. + Add support for device scan property. + Add support for additional access protocols. + Update the D-Bus policy configuration file. + +ver 2.23: + Update the new D-Bus interface. + Make dfutool ready for big endian architectures. + Add support for AVRCP specific service records. + Add support for writing complex BCCMD commands. + Add the new BCCMD interface utility. + Add MicroBCSP implementation from CSR. + Add constants and definitions for sniff subrating. + Add support for allocation of binary text elements. + Add HCI emulation tool. + Add fake HID support for old EPoX presenters. + Reject connections from unknown HID devices. + Fix service discovery deadlocks with Samsung D600 phones. + +ver 2.22: + Remove D-Bus 0.23 support. + Add initial version of the new D-Bus interface. + Add support for extended inquiry response commands. + Add support for the Logitech diNovo Media Desktop Laser. + Add compile time buffer checks (FORTIFY SOURCE). + Decode reserved LMP feature bits. + Fix errno overwrite problems. + Fix profile descriptor problem with Samsung phones. + +ver 2.21: + Move create_dirs() and create_file() into the textfile library. + Let textfile_put() also replace the last key value pair. + Fix memory leaks with textfile_get() usage. + Fix infinite loops and false positive matches. + Don't retrieve stored link keys for RAW devices. + Document the putkey and delkey commands. + Show supported commands also in clear text. + Support volatile changes of the BD_ADDR for CSR chips. + Add support for identification of supported commands. + Add missing OCF declarations for the security filter. + Add two new company identifiers. + +ver 2.20: + Add UUIDs for video distribution profile. + Add UUIDs for phonebook access profile. + Add attribute identifier for supported repositories. + Add definitions for extended inquiry response. + Add functions for extended inquiry response. + Add support for extended inquiry response. + Add support for HotSync service record. + Add support for ActiveSync service record. + Add ActiveSync networking support. + Fix D-Bus crashes with new API versions. + +ver 2.19: + Fix the GCC 4.0 warnings. + Fix the routing for dealing with raw devices. + Fix off by one memory allocation error. + Fix security problem with escape characters in device name. + Add per device service record functions. + Send D-Bus signals for inquiry results and remote name resolves. + Add support for device specific SDP records. + +ver 2.18: + Support D-Bus 0.23 and 0.33 API versions. + Support reading of complex BCCMD values. + Support minimum and maximum encryption key length. + Add support for reading and writing the inquiry scan type. + Add definitions for connection accept timeout and scan enable. + Add support for inquiry scan type. + Add tool for the CSR BCCMD interface. + Add first draft of the Audio/Video control utility. + Add disconnect timer support for the A2DP ALSA plugin. + Make SBC parameters configurable. + Replace non-printable characters in device names. + Remove hci_vhci.h header file. + Remove hci_uart.h header file. + +ver 2.17: + Set the storage directory through ${localstatedir}. + Add the textfile library for ASCII based file access. + Add support for return link keys event. + Add support for voice setting configuration. + Add support for page scan timeout configuration. + Add support for storing and deleting of stored link keys. + Add support for searching for services with UUID-128. + Add support for retrieving all possible service records. + Add support for a raw mode view of service records. + Add support for HID information caching in hidd. + Add support for authentication in pand and dund. + Add support for changing BD_ADDR of CSR chips. + Add pskey utility for changing CSR persistent storage values. + Add the firmware upgrade utility. + Add connection caching for the A2DP ALSA plugin. + Add functions for stored link keys. + Add definitions for PIN type and unit key. + Add SDP_WAIT_ON_CLOSE flag for sdp_connect(). + Include stdio.h in bluetooth.h header file. + Include sys/socket.h in the header files. + +ver 2.16: + Store link keys in ASCII based file format. + Support device name caching. + Support zero length data sizes in l2test. + Change default l2ping data size to 44 bytes. + Hide the server record and the public browse group root. + Read BD_ADDR if not set and if it is a raw device. + Add SDP language attributes. + Add support for browsing the L2CAP group. + Add support for stored pin codes for outgoing connections. + Add support for local commands and extended features. + Add support for reading CSR panic and fault codes. + Add config option for setting the inquiry mode. + Add OUI decoding support. + Use unlimited inquiry responses as default. + Use cached device names for PIN request. + Use the clock offset when getting the remote names. + Add function for reading local supported commands. + Add function for reading local extended features. + Add function for reading remote extended features. + Add function for getting the remote name with a clock offset. + Add function for extracting the OUI from a BD_ADDR. + Add inquiry info structure with RSSI and page scan mode. + Fix buffer allocation for features to string conversion. + Support inquiry with unlimited number of responses. + +ver 2.15: + Enable the RFCOMM service level security. + Add deprecated functions for reading the name. + Add command for reading the clock offset. + Add command for reading the clock. + Add function for reading the clock. + Add function for reading the local Bluetooth address. + Add function for reading the local supported features. + Don't configure raw devices. + Don't set inquiry scan or page scan on raw devices. + Don't show extended information for raw devices. + Support L2CAP signal sizes bigger than 2048 bytes. + Cleanup of the socket handling code of the test programs. + Use better way for unaligned access. + Remove sdp_internal.h and its usage. + +ver 2.14: + Make use of additional connection information. + Use library function for reading the RSSI. + Use library function for reading the link quality. + Use library function for reading the transmit power level. + Use library functions for the link supervision timeout. + Add tool for changing the device address. + Add function for reading the RSSI. + Add function for reading the link quality. + Add function for reading the transmit power level. + Add functions for the link supervision timeout. + Remove deprecated functions. + Update AM_PATH_BLUEZ macro. + +ver 2.13: + Use file permission 0600 for the link key file. + Add support for HID attribute descriptions. + Add support for Device ID attributes. + Add Device ID and HID attribute definitions. + Update the UUID constants and its translations. + Update L2CAP socket option definitions. + Update connection information definitions. + Various whitespace cleanups. + +ver 2.12: + Inherit the device specific options from the default. + Use --device for selecting the source device. + Add --nosdp option for devices with resource limitation. + Add support and parameter option for secure mode. + Add a lot of build ids and hardware revisions. + Add service classes and profile ids for WAP. + Add simple AM_PATH_BLUEZ macro. + Update UUID translation tables. + Correct kernel interface for CMTP and HIDP support. + +ver 2.11: + Initial support for the kernel security manager. + Various cleanups to avoid inclusion of kernel headers. + Fix output when the CUPS backend is called without arguments. + Fix problems with a 64 bit userland. + Use Bluetooth library functions if available. + Use standard numbering scheme of SDP record handles. + Use bit zero for vendor packets in the filter type bitmask. + Add SIM Access types for service discovery. + Add more audio/video profile translations. + Add another company identifier. + Add the missing HCI error codes. + Add RFCOMM socket options. + Add definition for the SECURE link mode. + Add functions for reading and writing the inquiry mode. + Add functions for AFH related settings and information. + Add version identifier for the Bluetooth 2.0 specification. + Add a master option to the hidd. + Add support for changing the link key of a connection. + Add support for requesting encryption on keyboards. + Add support for revision information of Digianswer devices. + Add support for the Zoom, IBM and TDK PCMCIA cards. + Add checks for the OpenOBEX and the ALSA libraries. + Add experimental mRouter support. + +ver 2.10: + Use a define for the configuration directory. + Fix string initialization for flags translation. + Fix and extend the unaligned access macros. + Make compiling with debug information optional. + Don't override CFLAGS from configure. + Check for usb_get_busses() and usb_interrupt_read(). + Add optional support for compiling with PIE. + Make installation of the init scripts optional. + Make compiling with debug information optional. + Don't override CFLAGS from configure. + +ver 2.9: + Retry SDP connect if busy in the CUPS backend. + Use packet type and allow role switch in hcitool. + Use the functions from the USB library for hid2hci. + Add Broadcom firmware loader. + Add EPoX endian quirk for buggy keyboards. + Add L2CAP info type and info result definitions. + Add value for L2CAP_CONF_RFC_MODE. + Change RSSI value to signed instead of unsigned. + Allow UUID32 values as protocol identifiers. + Update the autoconf/automake scripts. + +ver 2.8: + Use LIBS and LDADD instead of LDFLAGS. + Use HIDP subclass field for HID boot protocol. + Set olen before calling getsockopt() in pand. + Restore signals for dev-up script. + Add PID file support for pand. + Add size parameter to expand_name() in hcid. + Add support for audio source and audio sink SDP records. + Add support for HID virtual cable unplug. + Add support for AmbiCom BT2000C card. + Add defines and UUID's for audio/video profiles. + Add AVDTP protocol identifier. + Add HIDP subclass field. + Add PKGConfig support. + Fix the event code of inquiry with RSSI. + Remove dummy SDP library. + +ver 2.7: + Fix display of decoded LMP features. + Update company identifiers. + Add AFH related types. + Add first bits from EDR prototyping specification. + Add support for inquiry with RSSI. + Add HCRP related SDP functions. + Add HIDP header file. + Add support for getting the AFH channel map. + Add support for AFH mode. + Add support for inquiry mode. + Add Bluetooth backend for CUPS. + Add the hid2hci utility. + Add the hidd utility. + Add the pand utility. + Add the dund utility. + More endian bug fixes. + Give udev some time to create the RFCOMM device nodes. + Release the TTY if no device node is found. + New startup script for the Bluetooth subsystem. + Update to the autoconf stuff. + +ver 2.6: + Change default prefix to /usr. + Add manpages for hcid and hcid.conf. + Add the sdpd server daemon. + Add the sdptool utility. + Add the ciptool utility. + Add new company identifiers. + Add BNEP and CMTP header files. + Add the SDP library. + Use R2 for default value of pscan_rep_mode. + +ver 2.5: + Add decoding of Bluetooth 1.2 features. + Add link manager version parameter for Bluetooth 1.2. + Add new company identifiers. + Add D-Bus support for PIN request. + Support for transmit power level. + Support for park, sniff and hold mode. + Support for role switch. + Support for reading the clock offset. + Support for requesting authentication. + Support for setting connection encryption. + Show revision information for Broadcom devices. + Replace unprintable characters in device name. + Use R1 for default value of pscan_rep_mode. + Fix some 64-bit problems. + Fix some endian problems. + Report an error on PIN helper failure. + Update bluepin script for GTK2. + +ver 2.4: + Increase number of inquiry responses. + Support for transmit power level. + Display all 8 bytes of the features. + Add support for reading and writing of IAC. + Correct decoding class of device. + Use Ericsson revision command for ST Microelectronics devices. + Display AVM firmware version with 'revision' command. + New code for CSR specific revision information. + Support for ST Microelectronics specific initialization. + Support for 3Com card version 3.0. + Support for TDK, IBM and Socket cards. + Support for initial baud rate. + Update man pages. + Fixes for some memory leaks. + +ver 2.3: + Added const qualifiers to appropriate function arguments. + Minor fixes. + CSR firmware version is now displayed by 'revision' command. + Voice command is working properly on big endian machines. + Added support for Texas Bluetooth modules. + Added support for high UART baud rates on Ericsson modules. + BCSP initialization fixes. + Support for role switch command (hcitool). + RFCOMM config file parser fixes. + Update man pages. + Removed GLib dependency. + +ver 2.2: + Updated RFCOMM header file. + Additional HCI command and event defines. + Support for voice settings (hciconfig). + Minor hcitool fixes. + Improved configure script. + Added Headset testing tool. + Updated man pages. + RPM package. + +ver 2.1.1: + Resurrect hci_remote_name. + +ver 2.1: + Added hci_{read, write}_class_of_dev(). + Added hci_{read, write}_current_iac_lap(). + Added hci_write_local_name(). + Added RFCOMM header file. + Minor fixes. + Improved BCSP initialization (hciattach). + Support for displaying link quality (hcitool). + Support for changing link supervision timeout (hcitool). + New RFCOMM TTY configuration tool (rfcomm). + Minor fixes and updates. + +ver 2.0: + Additional company IDs. + BCSP initialization (hciattach). + Minor hciconfig fixes. + +ver 2.0-pr13: + Support for multiple pairing modes. + Link key database handling fixes. + +ver 2.0-pre12: + Removed max link key limit. Keys never expire. + Link key database is always updated. Reread PIN on SIGHUP (hcid). + Bluetooth script starts SDPd, if installed. + Other minor fixes. + +ver 2.0-pre11: + Improved link key management and more verbose logging (hcid). + Fixed scan command (hcitool). + +ver 2.0-pre10: + Fix hci_inquiry function to return errors and accept user buffers. + New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route. + Additional company IDs. + Makefile and other minor fixes. + Support for reading RSSI, remote name and changing + connection type (hcitool). + Device initialization fixes (hcid). + Other minor fixes and improvements. + Build environment cleanup and fixes. + +ver 2.0-pre9: + Improved bluepin. Working X authentication. + Improved hcitool. New flexible cmd syntax, additional commands. + Human readable display of the device features. + LMP features to string translation support. + Additional HCI command and event defines. + Extended hci_filter API. + +ver 2.0-pre8: + Additional HCI ioctls and defines. + All strings and buffers are allocated dynamically. + ba2str, str2ba automatically swap bdaddress. + Additional hciconfig commands. Support for ACL and SCO MTU ioctls. + Support for Inventel and COM1 UART based devices. + Minor hcitool fixes. + Improved l2test. New L2CAP test modes. + Minor fixes and cleanup. + +ver 2.0-pre7: + Bluetooth libraries and header files is now a separate package. + New build environment uses automake and libtool. + Massive header files cleanup. + Bluetooth utilities is now a separate package. + New build environment uses automake. + Moved all config files and security data to /etc/bluetooth. + Various cleanups. + +ver 2.0-pre6: + API cleanup and additions. + Improved hcitool. + l2test minor output fixes. + hciattach opt to display list of supported devices. + +ver 2.0-pre4: + HCI filter enhancements. + +ver 2.0-pre3: + Cleanup. + +ver 2.0-pre2: + Additional HCI library functions. + Improved CSR baud rate initialization. + PCMCIA scripts fixes and enhancements. + Documentation update. + +ver 2.0-pre1: + New UART initialization utility. + Hot plugging support for UART based PCMCIA devices. + SCO testing utility. + New authentication utility (bluepin). + Minor fixes and improvements.
diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..87762f1 --- /dev/null +++ b/HACKING
@@ -0,0 +1,157 @@ +Hacking on BlueZ +**************** + +Build tools requirements +======================== + +When building and testing directly from the repository it is important to +have at least automake version 1.10 or later installed. All modern +distributions should default to the latest version, but it seems that +Debian's default is still an earlier version: + + Check version + # dpkg -l '*automake*' + + Install new version + # apt-get install automake1.10 + # update-alternatives --config automake + + +Working with the source code repository +======================================= + +The repository contains two extra scripts that accomplish the bootstrap +process. One is called "bootstrap" which is the basic scripts that uses the +autotools scripts to create the needed files for building and installing. +It makes sure to call the right programs depending on the usage of shared or +static libraries or translations etc. + +The second program is called "bootstrap-configure". This program will make +sure to properly clean the repository, call the "bootstrap" script and then +call configure with proper settings for development. It will use the best +options and pass them over to configure. These options normally include +the enabling the maintainer mode and the debugging features. + +So while in a normal source project the call "./configure ..." is used to +configure the project with its settings like prefix and extra options. In +case of bare repositories call "./bootstrap-configure" and it will bootstrap +the repository and calls configure with all the correct options to make +development easier. + +In case of preparing for a release with "make distcheck", don't use +bootstrap-configure since it could export development specific settings. + +So the normal steps to checkout, build and install such a repository is +like this: + + Checkout repository + # git clone git://git.kernel.org/pub/scm/bluetooth/bluez.git + # cd bluez + + Configure and build + # ./bootstrap-configure + # make + + Configure and build with cgcc (Sparse) + # ./bootstrap-configure CC=cgcc + # make + + Run unit tests + # make check + + Check installation + # make install DESTDIR=$PWD/x + # find x + # rm -rf x + + Check distribution + # make distcheck + + Final installation + # sudo make install + + Remove autogenerated files + # make maintainer-clean + + +Running from within the source code repository +============================================== + +When using "./configure --enable-maintainer-mode" the automake scripts will +use the plugins directly from within the repository. This removes the need +to use "make install" when testing "bluetoothd". The "bootstrap-configure" +automatically includes this option. + + Copy configuration file which specifies the required security policies + # sudo cp ./src/bluetooth.conf /etc/dbus-1/system.d/ + + Run daemon in foreground with debugging + # sudo ./src/bluetoothd -n -d -f ./src/main.conf + + Run daemon with valgrind + # sudo valgrind --trace-children=yes --track-origins=yes --track-fds=yes \ + --show-possibly-lost=no --leak-check=full --suppressions=./tools/valgrind.supp \ + ./src/bluetoothd -n -d -f ./src/main.conf + +For production installations or distribution packaging it is important that +the "--enable-maintainer-mode" option is NOT used. + +Note multiple arguments to -d can be specified, colon, comma or space +separated. The arguments are relative source code filenames for which +debugging output should be enabled; output shell-style globs are +accepted (e.g.: 'plugins/*:src/main.c'). + +Submitting patches +================== + +If you fixed a bug or you want to add support for something, patches are +welcome! In order to ease the inclusion of your patch, it's important to follow +some rules, otherwise it will likely be rejected by maintainers. + +Make sure the author name and email are set properly: + + # git config --global user.name <name> + # git config --global user.email <email> + +The preferred way to send patches is by email, using git send-email: + + # git config --global sendemail.smtpencryption <tls> + # git config --global sendemail.smtpserver <smtp.gmail.com> + # git config --global sendemail.smtpuser <yourname@gmail.com> + # git config --global sendemail.smtpserverport <587> + # git config sendemail.to linux-bluetooth@vger.kernel.org + +BlueZ rules for submitting patches follow most of the rules used by Linux kernel +(https://www.kernel.org/doc/Documentation/SubmittingPatches) with some remarks: + +1) Do *not* add "Signed-off-by" lines in your commit messages. BlueZ does not +use them, so including them is actually an error. + +2) Be sure to follow the coding style rules of BlueZ. They are listed in +doc/coding-style.txt. + +3) Split your patch according to the top-level directories. E.g.: if you added +a feature that touches files under 'include/', 'src/' and 'drivers/' +directories, split in three separated patches, taking care not to +break compilation. + +4) Bug fixes should be sent first as they take priority over new features. + +5) The commit message should follow 50/72 formatting which means the header +should be limited to 50 characters and the description should be wrapped at 72 +characters except if it contains quoted information from debug tools like +backtraces, compiler errors, etc. + +6) Prefix the email subject with [PATCH BlueZ]: + + # git config format.subjectprefix "PATCH BlueZ" + +7) Add a cover letter when introducing a new feature explaning what problem +you're trying to solve: + + # git format-patch --cover-letter -M origin/master -o outgoing/ + # edit outgoing/0000-* + +8) Submit: + + # git send-email outgoing/*
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..8faabf4 --- /dev/null +++ b/Makefile.am
@@ -0,0 +1,522 @@ + +AM_MAKEFLAGS = --no-print-directory + +lib_LTLIBRARIES = + +noinst_LIBRARIES = + +noinst_LTLIBRARIES = + +bin_PROGRAMS = + +noinst_PROGRAMS = + +dist_man_MANS = + +dist_noinst_MANS = + +CLEANFILES = + +EXTRA_DIST = + +libexecdir = @libexecdir@/bluetooth + +libexec_PROGRAMS = + +includedir = @includedir@/bluetooth + +include_HEADERS = + +AM_CFLAGS = $(WARNING_CFLAGS) $(MISC_CFLAGS) +AM_LDFLAGS = $(MISC_LDFLAGS) + +if DATAFILES +dbusdir = @DBUS_CONFDIR@/dbus-1/system.d +dbus_DATA = src/bluetooth.conf + +confdir = $(sysconfdir)/bluetooth +conf_DATA = + +statedir = $(localstatedir)/lib/bluetooth +state_DATA = +endif + +if SYSTEMD +systemdsystemunitdir = @SYSTEMD_SYSTEMUNITDIR@ +systemdsystemunit_DATA = src/bluetooth.service + +dbussystembusdir = @DBUS_SYSTEMBUSDIR@ +dbussystembus_DATA = src/org.bluez.service +endif + +EXTRA_DIST += src/bluetooth.service.in src/org.bluez.service + +plugindir = $(libdir)/bluetooth/plugins + +if MAINTAINER_MODE +build_plugindir = $(abs_top_srcdir)/plugins/.libs +else +build_plugindir = $(plugindir) +endif + + +plugin_LTLIBRARIES = + +lib_sources = lib/bluetooth.c lib/hci.c lib/sdp.c +lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h \ + lib/sco.h lib/l2cap.h lib/sdp.h lib/sdp_lib.h \ + lib/rfcomm.h lib/bnep.h lib/cmtp.h lib/hidp.h + +extra_headers = lib/mgmt.h lib/uuid.h lib/a2mp.h lib/amp.h +extra_sources = lib/uuid.c + +local_headers = $(foreach file,$(lib_headers), lib/bluetooth/$(notdir $(file))) + +BUILT_SOURCES = $(local_headers) src/builtin.h + +if LIBRARY +include_HEADERS += $(lib_headers) + +lib_LTLIBRARIES += lib/libbluetooth.la + +lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) +lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 21:16:18 +lib_libbluetooth_la_DEPENDENCIES = $(local_headers) +endif + +noinst_LTLIBRARIES += lib/libbluetooth-internal.la + +lib_libbluetooth_internal_la_SOURCES = $(lib_headers) $(lib_sources) \ + $(extra_headers) $(extra_sources) + +noinst_LTLIBRARIES += gdbus/libgdbus-internal.la + +gdbus_libgdbus_internal_la_SOURCES = gdbus/gdbus.h \ + gdbus/mainloop.c gdbus/watch.c \ + gdbus/object.c gdbus/client.c gdbus/polkit.c + +noinst_LTLIBRARIES += src/libshared-glib.la src/libshared-mainloop.la + +shared_sources = src/shared/io.h src/shared/timeout.h \ + src/shared/queue.h src/shared/queue.c \ + src/shared/util.h src/shared/util.c \ + src/shared/mgmt.h src/shared/mgmt.c \ + src/shared/crypto.h src/shared/crypto.c \ + src/shared/ecc.h src/shared/ecc.c \ + src/shared/ringbuf.h src/shared/ringbuf.c \ + src/shared/tester.h src/shared/tester.c \ + src/shared/hci.h src/shared/hci.c \ + src/shared/hci-crypto.h src/shared/hci-crypto.c \ + src/shared/hfp.h src/shared/hfp.c \ + src/shared/uhid.h src/shared/uhid.c \ + src/shared/pcap.h src/shared/pcap.c \ + src/shared/btsnoop.h src/shared/btsnoop.c \ + src/shared/ad.h src/shared/ad.c \ + src/shared/att-types.h \ + src/shared/att.h src/shared/att.c \ + src/shared/gatt-helpers.h src/shared/gatt-helpers.c \ + src/shared/gatt-client.h src/shared/gatt-client.c \ + src/shared/gatt-server.h src/shared/gatt-server.c \ + src/shared/gatt-db.h src/shared/gatt-db.c \ + src/shared/gap.h src/shared/gap.c \ + src/shared/tty.h + +src_libshared_glib_la_SOURCES = $(shared_sources) \ + src/shared/io-glib.c \ + src/shared/timeout-glib.c + +src_libshared_mainloop_la_SOURCES = $(shared_sources) \ + src/shared/io-mainloop.c \ + src/shared/timeout-mainloop.c \ + src/shared/mainloop.h src/shared/mainloop.c + +attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \ + attrib/gatt.h attrib/gatt.c \ + attrib/gattrib.h attrib/gattrib.c \ + attrib/gatt-service.h attrib/gatt-service.c + +btio_sources = btio/btio.h btio/btio.c + +gobex_sources = gobex/gobex.h gobex/gobex.c \ + gobex/gobex-defs.h gobex/gobex-defs.c \ + gobex/gobex-packet.c gobex/gobex-packet.h \ + gobex/gobex-header.c gobex/gobex-header.h \ + gobex/gobex-transfer.c gobex/gobex-debug.h \ + gobex/gobex-apparam.c gobex/gobex-apparam.h + +builtin_modules = +builtin_sources = +builtin_nodist = +builtin_ldadd = + +include Makefile.plugins + +if MAINTAINER_MODE +plugin_LTLIBRARIES += plugins/external-dummy.la +plugins_external_dummy_la_SOURCES = plugins/external-dummy.c +plugins_external_dummy_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined +plugins_external_dummy_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden +endif + +libexec_PROGRAMS += src/bluetoothd + +src_bluetoothd_SOURCES = $(builtin_sources) \ + $(attrib_sources) $(btio_sources) \ + src/bluetooth.ver \ + src/main.c src/log.h src/log.c \ + src/backtrace.h src/backtrace.c \ + src/systemd.h src/systemd.c \ + src/rfkill.c src/hcid.h src/sdpd.h \ + src/sdpd-server.c src/sdpd-request.c \ + src/sdpd-service.c src/sdpd-database.c \ + src/attrib-server.h src/attrib-server.c \ + src/gatt-database.h src/gatt-database.c \ + src/sdp-xml.h src/sdp-xml.c \ + src/sdp-client.h src/sdp-client.c \ + src/textfile.h src/textfile.c \ + src/uuid-helper.h src/uuid-helper.c \ + src/uinput.h \ + src/plugin.h src/plugin.c \ + src/storage.h src/storage.c \ + src/advertising.h src/advertising.c \ + src/agent.h src/agent.c \ + src/error.h src/error.c \ + src/adapter.h src/adapter.c \ + src/profile.h src/profile.c \ + src/service.h src/service.c \ + src/gatt-client.h src/gatt-client.c \ + src/device.h src/device.c \ + src/dbus-common.c src/dbus-common.h \ + src/eir.h src/eir.c +src_bluetoothd_LDADD = lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la \ + src/libshared-glib.la \ + @BACKTRACE_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ -ldl -lrt \ + $(builtin_ldadd) +src_bluetoothd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic \ + -Wl,--version-script=$(srcdir)/src/bluetooth.ver + +src_bluetoothd_DEPENDENCIES = lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la \ + src/libshared-glib.la \ + src/bluetooth.service + +src_bluetoothd_CFLAGS = $(AM_CFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \ + -DPLUGINDIR=\""$(build_plugindir)"\" +src_bluetoothd_SHORTNAME = bluetoothd + +builtin_files = src/builtin.h $(builtin_nodist) + +nodist_src_bluetoothd_SOURCES = $(builtin_files) + +CLEANFILES += $(builtin_files) src/bluetooth.service + +man_MANS = src/bluetoothd.8 + +EXTRA_DIST += src/genbuiltin src/bluetooth.conf \ + src/main.conf profiles/network/network.conf \ + profiles/input/input.conf + +test_scripts = +unit_tests = + +include Makefile.tools +include Makefile.obexd +include android/Makefile.am + +if HID2HCI +rulesdir = @UDEV_DIR@/rules.d + +rules_DATA = tools/97-hid2hci.rules + +CLEANFILES += $(rules_DATA) +endif + +EXTRA_DIST += tools/hid2hci.rules + +if TEST +testdir = $(pkglibdir)/test +test_SCRIPTS = $(test_scripts) +endif + +EXTRA_DIST += $(test_scripts) + +EXTRA_DIST += doc/assigned-numbers.txt doc/supported-features.txt \ + doc/test-coverage.txt \ + doc/test-runner.txt \ + doc/settings-storage.txt + +EXTRA_DIST += doc/mgmt-api.txt \ + doc/adapter-api.txt doc/device-api.txt \ + doc/agent-api.txt doc/profile-api.txt \ + doc/network-api.txt doc/media-api.txt \ + doc/health-api.txt doc/sap-api.txt \ + doc/input-api.txt + +EXTRA_DIST += doc/gatt-api.txt doc/advertising-api.txt + +EXTRA_DIST += doc/obex-api.txt doc/obex-agent-api.txt + +EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \ + doc/pts-opp.txt + +EXTRA_DIST += doc/btsnoop.txt + +EXTRA_DIST += tools/magic.btsnoop + +AM_CFLAGS += @DBUS_CFLAGS@ @GLIB_CFLAGS@ + +AM_CPPFLAGS = -I$(builddir)/lib + + +unit_tests += unit/test-eir + +unit_test_eir_SOURCES = unit/test-eir.c src/eir.c src/uuid-helper.c +unit_test_eir_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ + @GLIB_LIBS@ + +unit_tests += unit/test-uuid + +unit_test_uuid_SOURCES = unit/test-uuid.c +unit_test_uuid_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ + @GLIB_LIBS@ + +unit_tests += unit/test-textfile + +unit_test_textfile_SOURCES = unit/test-textfile.c src/textfile.h src/textfile.c +unit_test_textfile_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-crc + +unit_test_crc_SOURCES = unit/test-crc.c monitor/crc.h monitor/crc.c +unit_test_crc_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-crypto + +unit_test_crypto_SOURCES = unit/test-crypto.c +unit_test_crypto_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-ecc + +unit_test_ecc_SOURCES = unit/test-ecc.c +unit_test_ecc_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-ringbuf unit/test-queue + +unit_test_ringbuf_SOURCES = unit/test-ringbuf.c +unit_test_ringbuf_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_test_queue_SOURCES = unit/test-queue.c +unit_test_queue_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-mgmt + +unit_test_mgmt_SOURCES = unit/test-mgmt.c +unit_test_mgmt_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-uhid + +unit_test_uhid_SOURCES = unit/test-uhid.c +unit_test_uhid_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-sdp + +unit_test_sdp_SOURCES = unit/test-sdp.c \ + src/sdpd.h src/sdpd-database.c \ + src/log.h src/log.c \ + src/sdpd-service.c src/sdpd-request.c +unit_test_sdp_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-avdtp + +unit_test_avdtp_SOURCES = unit/test-avdtp.c \ + src/log.h src/log.c \ + android/avdtp.c android/avdtp.h +unit_test_avdtp_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-avctp + +unit_test_avctp_SOURCES = unit/test-avctp.c \ + src/log.h src/log.c \ + android/avctp.c android/avctp.h +unit_test_avctp_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-avrcp + +unit_test_avrcp_SOURCES = unit/test-avrcp.c \ + src/log.h src/log.c \ + android/avctp.c android/avctp.h \ + android/avrcp-lib.c android/avrcp-lib.h +unit_test_avrcp_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-hfp + +unit_test_hfp_SOURCES = unit/test-hfp.c +unit_test_hfp_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +unit_tests += unit/test-gdbus-client + +unit_test_gdbus_client_SOURCES = unit/test-gdbus-client.c +unit_test_gdbus_client_LDADD = gdbus/libgdbus-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ @DBUS_LIBS@ + +unit_tests += unit/test-gobex-header unit/test-gobex-packet unit/test-gobex \ + unit/test-gobex-transfer unit/test-gobex-apparam + +unit_test_gobex_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ + unit/test-gobex.c +unit_test_gobex_LDADD = @GLIB_LIBS@ + +unit_test_gobex_packet_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ + unit/test-gobex-packet.c +unit_test_gobex_packet_LDADD = @GLIB_LIBS@ + +unit_test_gobex_header_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ + unit/test-gobex-header.c +unit_test_gobex_header_LDADD = @GLIB_LIBS@ + +unit_test_gobex_transfer_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ + unit/test-gobex-transfer.c +unit_test_gobex_transfer_LDADD = @GLIB_LIBS@ + +unit_test_gobex_apparam_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ + unit/test-gobex-apparam.c +unit_test_gobex_apparam_LDADD = @GLIB_LIBS@ + +unit_tests += unit/test-lib + +unit_test_lib_SOURCES = unit/test-lib.c +unit_test_lib_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la @GLIB_LIBS@ + +unit_tests += unit/test-gatt + +unit_test_gatt_SOURCES = unit/test-gatt.c +unit_test_gatt_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la @GLIB_LIBS@ + +unit_tests += unit/test-hog + +unit_test_hog_SOURCES = unit/test-hog.c \ + $(btio_sources) \ + profiles/input/hog-lib.h profiles/input/hog-lib.c \ + profiles/scanparam/scpp.h profiles/scanparam/scpp.c \ + profiles/battery/bas.h profiles/battery/bas.c \ + profiles/deviceinfo/dis.h profiles/deviceinfo/dis.c \ + src/log.h src/log.c \ + attrib/att.h attrib/att.c \ + attrib/gatt.h attrib/gatt.c \ + attrib/gattrib.h attrib/gattrib.c +unit_test_hog_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la @GLIB_LIBS@ + +unit_tests += unit/test-gattrib + +unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c $(btio_sources) src/log.h src/log.c +unit_test_gattrib_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la \ + @GLIB_LIBS@ @DBUS_LIBS@ -ldl -lrt + +if MIDI +unit_tests += unit/test-midi +unit_test_midi_CFLAGS = $(AM_CFLAGS) @ALSA_CFLAGS@ -DMIDI_TEST +unit_test_midi_SOURCES = unit/test-midi.c \ + profiles/midi/libmidi.h \ + profiles/midi/libmidi.c +unit_test_midi_LDADD = src/libshared-glib.la \ + @GLIB_LIBS@ @ALSA_LIBS@ +endif + +if MAINTAINER_MODE +noinst_PROGRAMS += $(unit_tests) +endif + +TESTS = $(unit_tests) +AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69 + +if DBUS_RUN_SESSION +AM_TESTS_ENVIRONMENT += dbus-run-session -- +endif + +if VALGRIND +LOG_COMPILER = valgrind --error-exitcode=1 --num-callers=30 +LOG_FLAGS = --trace-children=yes --leak-check=full --show-reachable=no \ + --suppressions=$(srcdir)/tools/valgrind.supp --quiet +endif + +pkgconfigdir = $(libdir)/pkgconfig + +if LIBRARY +pkgconfig_DATA = lib/bluez.pc +endif + +manual_pages = doc/btmon.1 + +if MANPAGES +dist_noinst_MANS += $(manual_pages) +endif + +EXTRA_DIST += $(manual_pages:.1=.txt) + +DISTCHECK_CONFIGURE_FLAGS = --disable-datafiles --enable-library \ + --enable-health \ + --enable-mesh \ + --enable-midi \ + --enable-manpages \ + --enable-android \ + --disable-systemd \ + --disable-udev + +DISTCLEANFILES = $(pkgconfig_DATA) $(unit_tests) $(manual_pages) + +MAINTAINERCLEANFILES = Makefile.in \ + aclocal.m4 configure config.h.in config.sub config.guess \ + ltmain.sh depcomp compile missing install-sh mkinstalldirs test-driver + +SED_PROCESS = $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(SED) -e 's,@libexecdir\@,$(libexecdir),g' \ + < $< > $@ + +%.service: %.service.in Makefile + $(SED_PROCESS) + +%.1: %.txt + $(AM_V_GEN)a2x --doctype manpage --format manpage $(srcdir)/$< + +src/builtin.h: src/genbuiltin $(builtin_sources) + $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@ + +tools/%.rules: + $(AM_V_GEN)cp $(srcdir)/$(subst 97-,,$@) $@ + +$(lib_libbluetooth_la_OBJECTS): $(local_headers) + +lib/bluetooth/%.h: lib/%.h + $(AM_V_at)$(MKDIR_P) lib/bluetooth + $(AM_V_GEN)$(LN_S) -f $(abspath $<) $@ + +if COVERAGE +clean-coverage: + @lcov --directory $(top_builddir) --zerocounters + $(RM) -r coverage $(top_builddir)/coverage.info + +coverage: check + @lcov --compat-libtool --directory $(top_builddir) --capture \ + --output-file $(top_builddir)/coverage.info + $(AM_V_at)$(MKDIR_P) coverage + @genhtml -o coverage/ $(top_builddir)/coverage.info + +clean-local: clean-coverage + -find $(top_builddir) -name "*.gcno" -delete + -find $(top_builddir) -name "*.gcda" -delete + $(RM) -r lib/bluetooth + +else +clean-local: + -find $(top_builddir) -name "*.gcno" -delete + -find $(top_builddir) -name "*.gcda" -delete + $(RM) -r lib/bluetooth +endif
diff --git a/Makefile.obexd b/Makefile.obexd new file mode 100644 index 0000000..2e33cbc --- /dev/null +++ b/Makefile.obexd
@@ -0,0 +1,110 @@ + +if SYSTEMD +systemduserunitdir = @SYSTEMD_USERUNITDIR@ +systemduserunit_DATA = obexd/src/obex.service + +dbussessionbusdir = @DBUS_SESSIONBUSDIR@ +dbussessionbus_DATA = obexd/src/org.bluez.obex.service +endif + +EXTRA_DIST += obexd/src/obex.service.in obexd/src/org.bluez.obex.service + +obex_plugindir = $(libdir)/obex/plugins + +obexd_builtin_modules = +obexd_builtin_sources = +obexd_builtin_nodist = + +obexd_builtin_modules += filesystem +obexd_builtin_sources += obexd/plugins/filesystem.c obexd/plugins/filesystem.h + +obexd_builtin_modules += bluetooth +obexd_builtin_sources += obexd/plugins/bluetooth.c + +if EXPERIMENTAL +obexd_builtin_modules += pcsuite +obexd_builtin_sources += obexd/plugins/pcsuite.c +endif + +obexd_builtin_modules += opp +obexd_builtin_sources += obexd/plugins/opp.c + +obexd_builtin_modules += ftp +obexd_builtin_sources += obexd/plugins/ftp.c obexd/plugins/ftp.h + +if OBEX +obexd_builtin_modules += irmc +obexd_builtin_sources += obexd/plugins/irmc.c + +obexd_builtin_modules += pbap +obexd_builtin_sources += obexd/plugins/pbap.c \ + obexd/plugins/vcard.h obexd/plugins/vcard.c \ + obexd/plugins/phonebook.h \ + obexd/plugins/phonebook-dummy.c +endif + +obexd_builtin_modules += mas +obexd_builtin_sources += obexd/plugins/mas.c obexd/src/map_ap.h \ + obexd/plugins/messages.h \ + obexd/plugins/messages-dummy.c + +obexd_builtin_modules += mns +obexd_builtin_sources += obexd/client/mns.c obexd/src/map_ap.h \ + obexd/client/map-event.h + +libexec_PROGRAMS += obexd/src/obexd + +obexd_src_obexd_SOURCES = $(btio_sources) $(gobex_sources) \ + $(obexd_builtin_sources) \ + obexd/src/main.c obexd/src/obexd.h \ + obexd/src/plugin.h obexd/src/plugin.c \ + obexd/src/log.h obexd/src/log.c \ + obexd/src/manager.h obexd/src/manager.c \ + obexd/src/obex.h obexd/src/obex.c obexd/src/obex-priv.h \ + obexd/src/mimetype.h obexd/src/mimetype.c \ + obexd/src/service.h obexd/src/service.c \ + obexd/src/transport.h obexd/src/transport.c \ + obexd/src/server.h obexd/src/server.c \ + obexd/client/manager.h obexd/client/manager.c \ + obexd/client/session.h obexd/client/session.c \ + obexd/client/bluetooth.h obexd/client/bluetooth.c \ + obexd/client/sync.h obexd/client/sync.c \ + obexd/client/pbap.h obexd/client/pbap.c \ + obexd/client/ftp.h obexd/client/ftp.c \ + obexd/client/opp.h obexd/client/opp.c \ + obexd/client/map.h obexd/client/map.c \ + obexd/client/map-event.h obexd/client/map-event.c \ + obexd/client/transfer.h obexd/client/transfer.c \ + obexd/client/transport.h obexd/client/transport.c \ + obexd/client/dbus.h obexd/client/dbus.c \ + obexd/client/driver.h obexd/client/driver.c \ + obexd/src/map_ap.h +obexd_src_obexd_LDADD = lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la \ + @ICAL_LIBS@ @DBUS_LIBS@ @GLIB_LIBS@ -ldl + +obexd_src_obexd_LDFLAGS = -Wl,--export-dynamic + +obexd_src_obexd_CFLAGS = $(AM_CFLAGS) @GLIB_CFLAGS@ @DBUS_CFLAGS@ \ + @ICAL_CFLAGS@ -DOBEX_PLUGIN_BUILTIN \ + -DPLUGINDIR=\""$(obex_plugindir)"\" \ + -fPIC -D_FILE_OFFSET_BITS=64 + +obexd_src_obexd_CPPFLAGS = -I$(builddir)/lib -I$(builddir)/obexd/src + +obexd_src_obexd_SHORTNAME = obexd + +obexd_builtin_files = obexd/src/builtin.h $(obexd_builtin_nodist) + +nodist_obexd_src_obexd_SOURCES = $(obexd_builtin_files) + +BUILT_SOURCES += obexd/src/builtin.h + +obexd/src/plugin.$(OBJEXT): obexd/src/builtin.h + +obexd/src/builtin.h: obexd/src/genbuiltin $(obexd_builtin_sources) + $(AM_V_GEN)$(srcdir)/obexd/src/genbuiltin $(obexd_builtin_modules) > $@ + +CLEANFILES += obexd/src/builtin.h $(builtin_files) obexd/src/obex.service + +EXTRA_DIST += obexd/src/genbuiltin
diff --git a/Makefile.plugins b/Makefile.plugins new file mode 100644 index 0000000..73377e5 --- /dev/null +++ b/Makefile.plugins
@@ -0,0 +1,109 @@ + +builtin_modules += hostname +builtin_sources += plugins/hostname.c + +builtin_modules += wiimote +builtin_sources += plugins/wiimote.c + +builtin_modules += autopair +builtin_sources += plugins/autopair.c + +builtin_modules += policy +builtin_sources += plugins/policy.c + +if NFC +builtin_modules += neard +builtin_sources += plugins/neard.c +endif + +if SAP +builtin_modules += sap +builtin_sources += profiles/sap/main.c profiles/sap/manager.h \ + profiles/sap/manager.c profiles/sap/server.h \ + profiles/sap/server.c profiles/sap/sap.h \ + profiles/sap/sap-dummy.c +endif + +if A2DP +builtin_modules += a2dp +builtin_sources += profiles/audio/source.h profiles/audio/source.c \ + profiles/audio/sink.h profiles/audio/sink.c \ + profiles/audio/a2dp.h profiles/audio/a2dp.c \ + profiles/audio/avdtp.h profiles/audio/avdtp.c \ + profiles/audio/media.h profiles/audio/media.c \ + profiles/audio/transport.h profiles/audio/transport.c \ + profiles/audio/a2dp-codecs.h +endif + + +if AVRCP +builtin_modules += avrcp +builtin_sources += profiles/audio/control.h profiles/audio/control.c \ + profiles/audio/avctp.h profiles/audio/avctp.c \ + profiles/audio/avrcp.h profiles/audio/avrcp.c \ + profiles/audio/player.h profiles/audio/player.c +endif + +if NETWORK +builtin_modules += network +builtin_sources += profiles/network/manager.c \ + profiles/network/bnep.h profiles/network/bnep.c \ + profiles/network/server.h profiles/network/server.c \ + profiles/network/connection.h \ + profiles/network/connection.c +endif + +if HID +builtin_modules += input +builtin_sources += profiles/input/manager.c \ + profiles/input/server.h profiles/input/server.c \ + profiles/input/device.h profiles/input/device.c \ + profiles/input/hidp_defs.h +endif + +if HOG +builtin_modules += hog +builtin_sources += profiles/input/hog.c profiles/input/uhid_copy.h \ + profiles/input/hog-lib.c profiles/input/hog-lib.h \ + profiles/deviceinfo/dis.c profiles/deviceinfo/dis.h \ + profiles/battery/bas.c profiles/battery/bas.h \ + profiles/scanparam/scpp.c profiles/scanparam/scpp.h \ + profiles/input/suspend.h profiles/input/suspend-none.c + +EXTRA_DIST += profiles/input/suspend-dummy.c +endif + +if HEALTH +builtin_modules += health +builtin_sources += profiles/health/mcap.h profiles/health/mcap.c \ + profiles/health/hdp_main.c profiles/health/hdp_types.h \ + profiles/health/hdp_manager.h \ + profiles/health/hdp_manager.c \ + profiles/health/hdp.h profiles/health/hdp.c \ + profiles/health/hdp_util.h profiles/health/hdp_util.c +endif + +builtin_modules += gap +builtin_sources += profiles/gap/gas.c + +builtin_modules += scanparam +builtin_sources += profiles/scanparam/scan.c + +builtin_modules += deviceinfo +builtin_sources += profiles/deviceinfo/deviceinfo.c + +if MIDI +builtin_modules += midi +builtin_sources += profiles/midi/midi.c \ + profiles/midi/libmidi.h \ + profiles/midi/libmidi.c +builtin_ldadd += @ALSA_LIBS@ +endif + +if SIXAXIS +plugin_LTLIBRARIES += plugins/sixaxis.la +plugins_sixaxis_la_SOURCES = plugins/sixaxis.c +plugins_sixaxis_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined @UDEV_LIBS@ +plugins_sixaxis_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden @UDEV_CFLAGS@ +endif
diff --git a/Makefile.tools b/Makefile.tools new file mode 100644 index 0000000..561302f --- /dev/null +++ b/Makefile.tools
@@ -0,0 +1,448 @@ + +if CLIENT +bin_PROGRAMS += client/bluetoothctl + +client_bluetoothctl_SOURCES = client/main.c \ + client/display.h client/display.c \ + client/agent.h client/agent.c \ + client/advertising.h \ + client/advertising.c \ + client/gatt.h client/gatt.c \ + monitor/uuid.h monitor/uuid.c +client_bluetoothctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ + @GLIB_LIBS@ @DBUS_LIBS@ -lreadline +endif + +if MESH +bin_PROGRAMS += mesh/meshctl + +mesh_meshctl_SOURCES = mesh/main.c \ + mesh/mesh-net.h \ + mesh/node.h mesh/node.c \ + mesh/gatt.h mesh/gatt.c \ + mesh/crypto.h mesh/crypto.c \ + mesh/keys.h \ + mesh/net.h mesh/net.c \ + mesh/prov.h mesh/prov.c \ + mesh/util.h mesh/util.c \ + mesh/agent.h mesh/agent.c \ + mesh/prov-db.h mesh/prov-db.c \ + mesh/config-model.h mesh/config-client.c \ + mesh/config-server.c \ + mesh/onoff-model.h mesh/onoff-model.c \ + client/display.h client/display.c \ + monitor/uuid.h monitor/uuid.c +mesh_meshctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ + lib/libbluetooth-internal.la \ + @GLIB_LIBS@ @DBUS_LIBS@ -ljson-c -lreadline +endif + +if MONITOR +bin_PROGRAMS += monitor/btmon + +monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \ + monitor/display.h monitor/display.c \ + monitor/hcidump.h monitor/hcidump.c \ + monitor/ellisys.h monitor/ellisys.c \ + monitor/control.h monitor/control.c \ + monitor/packet.h monitor/packet.c \ + monitor/vendor.h monitor/vendor.c \ + monitor/lmp.h monitor/lmp.c \ + monitor/crc.h monitor/crc.c \ + monitor/ll.h monitor/ll.c \ + monitor/l2cap.h monitor/l2cap.c \ + monitor/sdp.h monitor/sdp.c \ + monitor/avctp.h monitor/avctp.c \ + monitor/avdtp.h monitor/avdtp.c \ + monitor/a2dp.h monitor/a2dp.c \ + monitor/rfcomm.h monitor/rfcomm.c \ + monitor/bnep.h monitor/bnep.c \ + monitor/uuid.h monitor/uuid.c \ + monitor/hwdb.h monitor/hwdb.c \ + monitor/keys.h monitor/keys.c \ + monitor/analyze.h monitor/analyze.c \ + monitor/intel.h monitor/intel.c \ + monitor/broadcom.h monitor/broadcom.c \ + monitor/tty.h +monitor_btmon_LDADD = lib/libbluetooth-internal.la \ + src/libshared-mainloop.la @UDEV_LIBS@ +endif + +if TESTING +noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \ + peripheral/btsensor tools/3dsp \ + tools/mgmt-tester tools/gap-tester \ + tools/l2cap-tester tools/sco-tester \ + tools/smp-tester tools/hci-tester \ + tools/rfcomm-tester tools/bnep-tester \ + tools/userchan-tester + +emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \ + emulator/serial.h emulator/serial.c \ + emulator/server.h emulator/server.c \ + emulator/vhci.h emulator/vhci.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c \ + emulator/phy.h emulator/phy.c \ + emulator/amp.h emulator/amp.c \ + emulator/le.h emulator/le.c +emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +emulator_b1ee_SOURCES = emulator/b1ee.c +emulator_b1ee_LDADD = src/libshared-mainloop.la + +emulator_hfp_SOURCES = emulator/hfp.c +emulator_hfp_LDADD = src/libshared-mainloop.la + +peripheral_btsensor_SOURCES = peripheral/main.c \ + peripheral/efivars.h peripheral/efivars.c \ + peripheral/attach.h peripheral/attach.c \ + peripheral/log.h peripheral/log.c \ + peripheral/gap.h peripheral/gap.c \ + peripheral/gatt.h peripheral/gatt.c +peripheral_btsensor_LDADD = src/libshared-mainloop.la \ + lib/libbluetooth-internal.la + +tools_3dsp_SOURCES = tools/3dsp.c monitor/bt.h +tools_3dsp_LDADD = src/libshared-mainloop.la + +tools_mgmt_tester_SOURCES = tools/mgmt-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_mgmt_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +tools_l2cap_tester_SOURCES = tools/l2cap-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_rfcomm_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +tools_bnep_tester_SOURCES = tools/bnep-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_bnep_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +tools_smp_tester_SOURCES = tools/smp-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_smp_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +tools_gap_tester_SOURCES = tools/gap-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_gap_tester_LDADD = lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la \ + src/libshared-glib.la \ + @GLIB_LIBS@ @DBUS_LIBS@ + +tools_sco_tester_SOURCES = tools/sco-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_sco_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h +tools_hci_tester_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +tools_userchan_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ +endif + +if TOOLS +bin_PROGRAMS += tools/rctest tools/l2test tools/l2ping tools/bccmd \ + tools/bluemoon tools/hex2hcd tools/mpris-proxy \ + tools/btattach + +noinst_PROGRAMS += tools/bdaddr tools/avinfo tools/avtest \ + tools/scotest tools/amptest tools/hwdb \ + tools/hcieventmask tools/hcisecfilter \ + tools/btinfo tools/btconfig \ + tools/btsnoop tools/btproxy \ + tools/btiotest tools/bneptest tools/mcaptest \ + tools/cltest tools/oobtest tools/advtest \ + tools/seq2bseq tools/nokfw tools/create-image \ + tools/eddystone tools/ibeacon \ + tools/btgatt-client tools/btgatt-server \ + tools/test-runner tools/check-selftest \ + tools/gatt-service profiles/iap/iapd + +tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c +tools_bdaddr_LDADD = lib/libbluetooth-internal.la @UDEV_LIBS@ + +tools_avinfo_LDADD = lib/libbluetooth-internal.la + +tools_avtest_LDADD = lib/libbluetooth-internal.la + +tools_scotest_LDADD = lib/libbluetooth-internal.la + +tools_amptest_LDADD = lib/libbluetooth-internal.la + +tools_hwdb_LDADD = lib/libbluetooth-internal.la + +tools_hcieventmask_LDADD = lib/libbluetooth-internal.la + +tools_btinfo_SOURCES = tools/btinfo.c monitor/bt.h +tools_btinfo_LDADD = src/libshared-mainloop.la + +tools_btattach_SOURCES = tools/btattach.c monitor/bt.h +tools_btattach_LDADD = src/libshared-mainloop.la + +tools_btconfig_SOURCES = tools/btconfig.c +tools_btconfig_LDADD = src/libshared-mainloop.la + +tools_btsnoop_SOURCES = tools/btsnoop.c +tools_btsnoop_LDADD = src/libshared-mainloop.la + +tools_btproxy_SOURCES = tools/btproxy.c monitor/bt.h +tools_btproxy_LDADD = src/libshared-mainloop.la + +tools_btiotest_SOURCES = tools/btiotest.c btio/btio.h btio/btio.c +tools_btiotest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ + +tools_mcaptest_SOURCES = tools/mcaptest.c \ + btio/btio.h btio/btio.c \ + src/log.c src/log.h \ + profiles/health/mcap.h profiles/health/mcap.c +tools_mcaptest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ -lrt + +tools_bneptest_SOURCES = tools/bneptest.c \ + btio/btio.h btio/btio.c \ + src/log.h src/log.c \ + profiles/network/bnep.h profiles/network/bnep.c +tools_bneptest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ + +tools_cltest_SOURCES = tools/cltest.c +tools_cltest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +tools_oobtest_SOURCES = tools/oobtest.c +tools_oobtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +tools_advtest_SOURCES = tools/advtest.c +tools_advtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +tools_seq2bseq_SOURCES = tools/seq2bseq.c + +tools_nokfw_SOURCES = tools/nokfw.c + +tools_create_image_SOURCES = tools/create-image.c + +tools_eddystone_SOURCES = tools/eddystone.c monitor/bt.h +tools_eddystone_LDADD = src/libshared-mainloop.la + +tools_ibeacon_SOURCES = tools/ibeacon.c monitor/bt.h +tools_ibeacon_LDADD = src/libshared-mainloop.la + +tools_btgatt_client_SOURCES = tools/btgatt-client.c src/uuid-helper.c +tools_btgatt_client_LDADD = src/libshared-mainloop.la \ + lib/libbluetooth-internal.la + +tools_btgatt_server_SOURCES = tools/btgatt-server.c src/uuid-helper.c +tools_btgatt_server_LDADD = src/libshared-mainloop.la \ + lib/libbluetooth-internal.la + +tools_rctest_LDADD = lib/libbluetooth-internal.la + +tools_l2test_LDADD = lib/libbluetooth-internal.la + +tools_l2ping_LDADD = lib/libbluetooth-internal.la + +tools_bccmd_SOURCES = tools/bccmd.c tools/csr.h tools/csr.c \ + tools/csr_hci.c tools/csr_usb.c \ + tools/csr_h4.c tools/csr_3wire.c \ + tools/csr_bcsp.c tools/ubcsp.h tools/ubcsp.c +tools_bccmd_LDADD = lib/libbluetooth-internal.la + +tools_bluemoon_SOURCES = tools/bluemoon.c monitor/bt.h +tools_bluemoon_LDADD = src/libshared-mainloop.la + +tools_hex2hcd_SOURCES = tools/hex2hcd.c + +tools_mpris_proxy_SOURCES = tools/mpris-proxy.c +tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la @GLIB_LIBS@ @DBUS_LIBS@ + +tools_gatt_service_SOURCES = tools/gatt-service.c +tools_gatt_service_LDADD = @GLIB_LIBS@ @DBUS_LIBS@ gdbus/libgdbus-internal.la + +profiles_iap_iapd_SOURCES = profiles/iap/main.c +profiles_iap_iapd_LDADD = gdbus/libgdbus-internal.la @GLIB_LIBS@ @DBUS_LIBS@ + +dist_man_MANS += tools/rctest.1 tools/l2ping.1 tools/bccmd.1 tools/btattach.1 + +EXTRA_DIST += tools/bdaddr.1 + +if DEPRECATED +bin_PROGRAMS += tools/hciattach tools/hciconfig tools/hcitool tools/hcidump \ + tools/rfcomm tools/sdptool tools/ciptool + +tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \ + tools/hciattach_st.c \ + tools/hciattach_ti.c \ + tools/hciattach_tialt.c \ + tools/hciattach_ath3k.c \ + tools/hciattach_qualcomm.c \ + tools/hciattach_intel.c \ + tools/hciattach_bcm43xx.c +tools_hciattach_LDADD = lib/libbluetooth-internal.la + +tools_hciconfig_SOURCES = tools/hciconfig.c tools/csr.h tools/csr.c +tools_hciconfig_LDADD = lib/libbluetooth-internal.la + +tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c +tools_hcitool_LDADD = lib/libbluetooth-internal.la @UDEV_LIBS@ + +tools_hcidump_SOURCES = tools/hcidump.c \ + tools/parser/parser.h tools/parser/parser.c \ + tools/parser/lmp.c \ + tools/parser/hci.c \ + tools/parser/l2cap.h tools/parser/l2cap.c \ + tools/parser/amp.c \ + tools/parser/smp.c \ + tools/parser/att.c \ + tools/parser/sdp.h tools/parser/sdp.c \ + tools/parser/rfcomm.h tools/parser/rfcomm.c \ + tools/parser/bnep.c \ + tools/parser/cmtp.c \ + tools/parser/hidp.c \ + tools/parser/hcrp.c \ + tools/parser/avdtp.c \ + tools/parser/avctp.c \ + tools/parser/avrcp.c \ + tools/parser/sap.c \ + tools/parser/obex.c \ + tools/parser/capi.c \ + tools/parser/ppp.c \ + tools/parser/tcpip.c \ + tools/parser/ericsson.c \ + tools/parser/csr.c \ + tools/parser/bpa.c + +tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c +tools_sdptool_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ + +tools_ciptool_LDADD = lib/libbluetooth-internal.la +tools_hcidump_LDADD = lib/libbluetooth-internal.la + +tools_rfcomm_LDADD = lib/libbluetooth-internal.la + +dist_man_MANS += tools/hciattach.1 tools/hciconfig.1 \ + tools/hcitool.1 tools/hcidump.1 \ + tools/rfcomm.1 tools/sdptool.1 tools/ciptool.1 +else +EXTRA_DIST += tools/hciattach.1 tools/hciconfig.1 \ + tools/hcitool.1 tools/hcidump.1 \ + tools/rfcomm.1 tools/sdptool.1 tools/ciptool.1 +endif +else +EXTRA_DIST += tools/rctest.1 tools/l2ping.1 tools/bccmd.1 tools/btattach.1 +endif + +if HID2HCI +udevdir = @UDEV_DIR@ + +udev_PROGRAMS = tools/hid2hci + +tools_hid2hci_LDADD = @UDEV_LIBS@ + +dist_man_MANS += tools/hid2hci.1 +else +EXTRA_DIST += tools/hid2hci.1 +endif + +if READLINE +noinst_PROGRAMS += tools/btmgmt tools/obex-client-tool tools/obex-server-tool \ + tools/bluetooth-player tools/obexctl + +tools_obex_client_tool_SOURCES = $(gobex_sources) $(btio_sources) \ + tools/obex-client-tool.c +tools_obex_client_tool_LDADD = lib/libbluetooth-internal.la \ + @GLIB_LIBS@ -lreadline + +tools_obex_server_tool_SOURCES = $(gobex_sources) $(btio_sources) \ + tools/obex-server-tool.c +tools_obex_server_tool_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ + +tools_bluetooth_player_SOURCES = tools/bluetooth-player.c \ + client/display.h client/display.c +tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ + @GLIB_LIBS@ @DBUS_LIBS@ -lreadline + +tools_obexctl_SOURCES = tools/obexctl.c \ + client/display.h client/display.c +tools_obexctl_LDADD = gdbus/libgdbus-internal.la \ + @GLIB_LIBS@ @DBUS_LIBS@ -lreadline + +tools_btmgmt_SOURCES = tools/btmgmt.c src/uuid-helper.c client/display.c +tools_btmgmt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la \ + -lreadline +if DEPRECATED +noinst_PROGRAMS += attrib/gatttool + +attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \ + attrib/gattrib.c btio/btio.c \ + attrib/gatttool.h attrib/interactive.c \ + attrib/utils.c src/log.c client/display.c \ + client/display.h +attrib_gatttool_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ -lreadline + +endif +endif + +if CUPS +cupsdir = $(libdir)/cups/backend + +cups_PROGRAMS = profiles/cups/bluetooth + +profiles_cups_bluetooth_SOURCES = profiles/cups/main.c \ + profiles/cups/cups.h \ + profiles/cups/sdp.c \ + profiles/cups/spp.c \ + profiles/cups/hcrp.c + +profiles_cups_bluetooth_LDADD = @GLIB_LIBS@ @DBUS_LIBS@ \ + lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la +endif + +test_scripts += test/sap_client.py test/bluezutils.py \ + test/dbusdef.py test/monitor-bluetooth test/list-devices \ + test/test-discovery test/test-manager test/test-adapter \ + test/test-device test/simple-agent \ + test/simple-endpoint test/test-sap-server \ + test/test-network test/test-profile test/test-health \ + test/test-health-sink test/service-record.dtd \ + test/service-did.xml test/service-spp.xml test/service-opp.xml \ + test/service-ftp.xml test/simple-player test/test-nap \ + test/test-hfp test/opp-client test/ftp-client \ + test/pbap-client test/map-client test/example-advertisement \ + test/example-gatt-server test/example-gatt-client \ + test/test-gatt-profile
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..5da6029 --- /dev/null +++ b/README
@@ -0,0 +1,240 @@ +BlueZ - Bluetooth protocol stack for Linux +****************************************** + +Copyright (C) 2000-2001 Qualcomm Incorporated +Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> +Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org> + + +Compilation and installation +============================ + +In order to compile Bluetooth utilities you need following software packages: + - GCC compiler + - GLib library + - D-Bus library + - udev library (optional) + - readline (command line clients) + +To configure run: + ./configure --prefix=/usr --mandir=/usr/share/man \ + --sysconfdir=/etc --localstatedir=/var + +Configure automatically searches for all required components and packages. + +To compile and install run: + make && make install + + +Configuration and options +========================= + +For a working system, certain configuration options need to be enabled: + + --enable-library + + Enable installation of Bluetooth library + + By default the Bluetooth library is no longer installed. + + The user interfaces or command line utilities do not + require an installed Bluetooth library anymore. This + option is provided for legacy third party applications + that still depend on the library. + + When the library installation is enabled, it is a good + idea to use a separate bluez-library or libbluetooth + package for it. + + --disable-tools + + Disable support for Bluetooth utilities + + By default the Bluetooth utilities are built and also + installed. For production systems the tools are not + needed and this option allows to disable them to save + build time and disk space. + + When the tools are selected, it is a good idea to + use a separate bluez-tools package for them. + + --disable-cups + + Disable support for CUPS printer backend + + By default the printer backend for CUPS is build and + also installed. For systems that do not require printing + over Bluetooth, this options allows to disable it. + + When the CUPS backend is selected, it is a good idea to + use a separate bluez-cups package for it. + + --disable-monitor + + Disable support for the Bluetooth monitor utility + + By default the monitor utility is enabled. It provides + support for HCI level tracing and debugging. For systems + that don't require any kind of tracing or debugging + capabilities, this options allows to disable it. + + The monitor utility should be placed in the main package + along with the daemons. It is universally useful. + + --disable-client + + Disable support for the command line client + + By default the command line client is enabled and uses the + readline library. For specific systems where BlueZ is + configured by other means, the command line client can be + disabled and the dependency on readline is removed. + + The client should be placed in the main package along + with the daemons. It is universally useful. + + --disable-systemd + + Disable integration with systemd + + By default the integration with systemd is enabled and + installed. This gives the best integration into all + distributions based on systemd. + + This option is provided for distributions that do not + support systemd. In that case all integration with the + init system is up to the package. + + --disable-a2dp + + Disable A2DP profile + + By default bluetoothd supports A2DP profile using a built-in + plugin, this option disables it. + + This option is provided for distributions that do not have any + audio capabilities. + + --disable-avrcp + + Disable AVRCP profile + + By default bluetoothd supports AVRCP profile using a built-in + plugin, this option disables it. + + This option is provided for distributions that do not have any + audio capabilities. + + --disable-network + + Disable PANU, NAP, GN profiles + + By default bluetoothd supports PANU, NAP and GN profile using a + built-in plugin, this option disables it. + + This option is provided for distributions that do not have any + network capabilities. + + --disable-hid + + Disable HID profile + + By default bluetoothd supports HID profile using a built-in + plugin, this option disables it. + + This option is provided for distributions that do not have any + input capabilities. + + --disable-hog + + Disable HoG profile + + By default bluetoothd supports HoG profile using a built-in + plugin, this option disables it. + + This option is provided for distributions that do not have any + input capabilities. + + --enable-testing + + Enable testing tools + + By default tools used only for testing emulation are disabled. + This option can be used to enable them. + + It is not recommended to enable this option for production + systems. These tools may contain tests that depend on specific + environment or kernel features in development. + + --enable-experimental + + Enable experimental tools + + By default all tools that are still in development + are disabled. This option can be used to enable them. + + It is not recommended to enable this option for production + systems. The behavior of the experimental tools is unstable + and might still change. + + --enable-deprecated + + Enable deprecated tools + + By defauld all tools that are no longer maintained are + disabled. This option can be used to enable them. + + It is not recommended to enable this option for production + systems. The behavior of the deprecated tools may be unstable + or simply don't work anymore. + + --enable-nfc + + This option enable NFC pairing support. + + By default the integration with neard is disabled, this gives + the option to enable it in system where neard is supported. + + The plugin is built into bluetoothd therefore it does not need + to be package separately. + + --enable-sap + + This option enable SAP profile using sap plugin. + + By default sap plugin is disabled since it requires tight + integration with systems and is very rarely required. + + The plugin is built into bluetoothd therefore it does not need + to be package separately. + + --enable-health + + This option enable health profiles. + + By default health plugin is disabled since its profiles are + target for the health industry. + + The plugin is built into bluetoothd therefore it does not need + to be package separately. + + --enable-midi + + This option enable MIDI support via ALSA Sequencer. + + By default midi plugin is disabled since it still considered + experimental. When bluetoothd will create a new ALSA Sequencer + client and port for each device connected that supports the + MIDI GATT primary service. + + The plugin is built into bluetoothd therefore it does not need + to be package separately. + +Information +=========== + +Mailing lists: + linux-bluetooth@vger.kernel.org + +For additional information about the project visit BlueZ web site: + http://www.bluez.org
diff --git a/TODO b/TODO new file mode 100644 index 0000000..d88349e --- /dev/null +++ b/TODO
@@ -0,0 +1,188 @@ +Background +========== + +- Priority scale: High, Medium and Low + +- Complexity scale: C1, C2, C4 and C8. The complexity scale is exponential, + with complexity 1 being the lowest complexity. Complexity is a function + of both task 'complexity' and task 'scope'. + + The general rule of thumb is that a complexity 1 task should take 1-2 weeks + for a person very familiar with BlueZ codebase. Higher complexity tasks + require more time and have higher uncertainty. + + Higher complexity tasks should be refined into several lower complexity tasks + once the task is better understood. + +General +======= + +- UUID handling: Use the new functions created for UUID handling in all parts + of BlueZ code. Currently, the new bt_uuid_* functions are being used by + GATT-related code only. + + Priority: high + Complexity: C4 + +- Update PBAP client/server implementation to 1.2 and create necessary APIs for + new features it introduces. + + Priority: Medium + Complexity: C4 + +- Create GOEP unit tests based on its test specification: + + https://www.bluetooth.org/docman/handlers/DownloadDoc.ashx?doc_id=230559 + + Priority: Medium + Complexity: C2 + +- Function in src/adapter.c to convert old storage files to new ini-file format + should be removed 6-8 months after first BlueZ 5 release. + + Priority: Low + Complexity: C1 + +- Remove usage of symlinks for drivers, such as profiles/input/suspend.c and + profiles/sap/sap.c. Instead, select drivers at runtime by using config + options or probing for running D-Bus services (using e.g. + g_dbus_add_service_watch()). Idea first mentioned on + http://thread.gmane.org/gmane.linux.bluez.kernel/30175/focus=30190. + +- Reuse connection handling code of src/profile.c also for built-in profiles + so plugins would only need to register their btd_profile and the core takes + care of the rest including listen to the right channel and manages the sdp + record. Once btd_profile manages the connection it can also notify about + their state, this probably remove the need of having callbacks to + .connect/.disconnect since their state can be tracked, it also enables any + plugin to track any profile state change which can be useful for e.g. + a connection policy plugin in case one is needed. + + Priority: Low + Complexity: C2 + +- Add queueing support for src/agent.c, currently if there is any request + pending the code fail with error EBUSY which is very inconvenient. + + Priority: Low + Complexity: C2 + +Low Energy +========== + +- Connection modes. Adapter interface needs to be changed to manage + connection modes and adapter type. See Volume 3, Part C, section 9.3. + 1. Mode management: Peripheral / Central + + Priority: Medium + Complexity: C2 + +- Advertising data. The D-Bus interface needs to be updated to enable setting + scan response data, and to read the advertising and scan response data which + has been broadcast from other LE devices. + + Priority: Medium + Complexity: C2 + +- Static random address setup and storage. Once this address is written + in a given remote, the address can not be changed anymore. + + Priority: Low + Complexity: C1 + +- Device Name Characteristic is a GAP characteristic for Low Energy. This + characteristic shall be integrated/used in the discovery procedure. The + idea is to report the value of this characteristic using DeviceFound signals. + Discussion with the community is needed before to start this task. Other GAP + characteristics for LE needs to follow a similar approach. It is not clear + if all GAP characteristics can be exposed using properties instead of a primary + service characteristics. + See Volume 3, Part C, section 12.1 for more information. + + Priority: Low + Complexity: C2 + +ATT/GATT (new shared stack) +=========================== + +- Add complete GATT test coverage in unit/test-gatt following the GATT test + spec. This could use shared/gatt-client and shared/gatt-server at the same + time to test both against eachother. We should definitely have tests for + gatt-server and gatt-client simultaneously on one side of the connection. + + Priority: High + Complexity: C4 + +- Write an example using client D-Bus API using C. + + Priority: High + Complexity: C2 + +- Write an example using client D-Bus API using python. + + Priority: High + Complexity: C2 + +- Define packed structs for ATT protocol PDUs in shared/att-types to improve + readability. We should probably do this once there are extensive unit tests + for gatt-client/gatt-server so that we don't accidentally break working code. + + Priority: Medium + Complexity: C2 + +- Use struct iovec to pass around byte buffers that will be sent over the wire, + instead of passing uint8_t and size_t parameters everywhere. + + Priority: Medium + Complexity: C1 + +- Move all daemon plugins and profiles that are GATT based to use + shared/gatt-client instead of attrib/*. This is a complicated task that + potentially needs a new plugin/profile probing interface and a lot of + rewriting that can cause regressions in existing functionality. + + Priority: Medium + Complexity: C4 + +- Introduce a way for shared/gatt-server to check security permissions on the + current connection through bt_att. + + Priority: Medium + Complexity: C2 + +- Implement other low-priority ATT protocol operations for shared/gatt-server: + + Read Multiple Request + + Priority: Low + Complexity: C1 + +- Send out indications from the "Service Changed" characteristic upon + reconnection if a bonded device is not connected when the local database is + modified. + + Priority: High + Complexity: C2 + +- Unify the GATT server and client D-Bus implementations into a single module. + While these don't share a lot of code, keeping them all in src/gatt-dbus seems + to make more sense from an organizational perspective. + + Priority: Low + Complexity: C1 + +- Isolate all GATT code inside the daemon into its own module and perform + interaction with other modules (e.g. src/device.c) via callbacks. This + includes client/server management, tracking incoming/outgoing connections for + ATT, and callbacks to perform profile probing. + + Priority: Low + Complexity: C4 + +- Support included services in the GATT D-Bus client API. + + Priority: Medium + Complexity: C1 + +Management Interface +====================
diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..bc39c6d --- /dev/null +++ b/acinclude.m4
@@ -0,0 +1,62 @@ +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], [ + with_cflags="" + if (test "$USE_MAINTAINER_MODE" = "yes"); then + with_cflags="$with_cflags -Wall -Werror -Wextra" + with_cflags="$with_cflags -Wno-unused-parameter" + with_cflags="$with_cflags -Wno-missing-field-initializers" + with_cflags="$with_cflags -Wdeclaration-after-statement" + with_cflags="$with_cflags -Wmissing-declarations" + with_cflags="$with_cflags -Wredundant-decls" + with_cflags="$with_cflags -Wcast-align" + with_cflags="$with_cflags -Wswitch-enum" + with_cflags="$with_cflags -Wformat -Wformat-security" + with_cflags="$with_cflags -DG_DISABLE_DEPRECATED" + with_cflags="$with_cflags -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_28" + with_cflags="$with_cflags -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_28" + fi + AC_SUBST([WARNING_CFLAGS], $with_cflags) +]) + +AC_DEFUN([MISC_FLAGS], [ + misc_cflags="" + misc_ldflags="" + AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], + [disable code optimization through compiler]), [ + if (test "${enableval}" = "no"); then + misc_cflags="$misc_cflags -O0" + fi + ]) + AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], + [enable compiling with debugging information]), [ + if (test "${enableval}" = "yes" && + test "${ac_cv_prog_cc_g}" = "yes"); then + misc_cflags="$misc_cflags -g" + fi + ]) + AC_ARG_ENABLE(pie, AC_HELP_STRING([--enable-pie], + [enable position independent executables flag]), [ + if (test "${enableval}" = "yes" && + test "${ac_cv_prog_cc_pie}" = "yes"); then + misc_cflags="$misc_cflags -fPIC" + misc_ldflags="$misc_ldflags -pie" + fi + ]) + if (test "$enable_coverage" = "yes"); then + misc_cflags="$misc_cflags --coverage" + misc_ldflags="$misc_ldflags --coverage" + fi + AC_SUBST([MISC_CFLAGS], $misc_cflags) + AC_SUBST([MISC_LDFLAGS], $misc_ldflags) +])
diff --git a/android/Android.mk b/android/Android.mk new file mode 100644 index 0000000..38ef4aa --- /dev/null +++ b/android/Android.mk
@@ -0,0 +1,855 @@ +LOCAL_PATH := external/bluetooth + +# Retrieve BlueZ version from configure.ac file +BLUEZ_VERSION := `grep "^AC_INIT" $(LOCAL_PATH)/bluez/configure.ac | sed -e "s/.*,.\(.*\))/\1/"` + +ANDROID_VERSION := $(shell echo $(PLATFORM_VERSION) | awk -F. '{ printf "0x%02d%02d%02d",$$1,$$2,$$3 }') + +ANDROID_GE_5_0_0 := $(shell test `echo $$(($(ANDROID_VERSION)))` -lt `echo $$((0x050000))`; echo $$?) + +# Specify pathmap for glib and sbc +pathmap_INCL += glib:external/bluetooth/glib \ + sbc:external/bluetooth/sbc \ + +# Specify common compiler flags +BLUEZ_COMMON_CFLAGS := -DVERSION=\"$(BLUEZ_VERSION)\" \ + -DANDROID_VERSION=$(ANDROID_VERSION) \ + -DANDROID_STORAGEDIR=\"/data/misc/bluetooth\" \ + -DHAVE_LINUX_IF_ALG_H \ + -DHAVE_LINUX_TYPES_H \ + +# Enable warnings enabled in autotools build +BLUEZ_COMMON_CFLAGS += -Wall -Wextra \ + -Wdeclaration-after-statement \ + -Wmissing-declarations \ + -Wredundant-decls \ + -Wcast-align \ + +# Disable warnings enabled by Android but not enabled in autotools build +BLUEZ_COMMON_CFLAGS += -Wno-pointer-arith \ + -Wno-missing-field-initializers \ + -Wno-unused-parameter \ + +# +# Android BlueZ daemon (bluetoothd) +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/main.c \ + bluez/android/bluetooth.c \ + bluez/profiles/scanparam/scpp.c \ + bluez/profiles/deviceinfo/dis.c \ + bluez/profiles/battery/bas.c \ + bluez/profiles/input/hog-lib.c \ + bluez/android/hidhost.c \ + bluez/android/socket.c \ + bluez/android/ipc.c \ + bluez/android/avdtp.c \ + bluez/android/a2dp.c \ + bluez/android/a2dp-sink.c \ + bluez/android/avctp.c \ + bluez/android/avrcp.c \ + bluez/android/avrcp-lib.c \ + bluez/android/pan.c \ + bluez/android/handsfree.c \ + bluez/android/handsfree-client.c \ + bluez/android/gatt.c \ + bluez/android/health.c \ + bluez/android/sco.c \ + bluez/profiles/health/mcap.c \ + bluez/android/map-client.c \ + bluez/android/log.c \ + bluez/src/shared/mgmt.c \ + bluez/src/shared/util.c \ + bluez/src/shared/queue.c \ + bluez/src/shared/ringbuf.c \ + bluez/src/shared/hfp.c \ + bluez/src/shared/gatt-db.c \ + bluez/src/shared/io-glib.c \ + bluez/src/shared/timeout-glib.c \ + bluez/src/shared/crypto.c \ + bluez/src/shared/uhid.c \ + bluez/src/shared/att.c \ + bluez/src/sdpd-database.c \ + bluez/src/sdpd-service.c \ + bluez/src/sdpd-request.c \ + bluez/src/sdpd-server.c \ + bluez/src/uuid-helper.c \ + bluez/src/eir.c \ + bluez/lib/sdp.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + bluez/lib/uuid.c \ + bluez/btio/btio.c \ + bluez/src/sdp-client.c \ + bluez/profiles/network/bnep.c \ + bluez/attrib/gattrib.c \ + bluez/attrib/gatt.c \ + bluez/attrib/att.c + +LOCAL_C_INCLUDES := \ + $(call include-path-for, glib) \ + $(call include-path-for, glib)/glib \ + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_SHARED_LIBRARIES := \ + libglib \ + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_TAGS := optional + +# for userdebug/eng this module is bluetoothd-main since bluetoothd is used as +# wrapper to launch bluetooth with Valgrind +ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) +LOCAL_MODULE := bluetoothd-main +LOCAL_STRIP_MODULE := false +else +LOCAL_MODULE := bluetoothd +endif + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# bluetooth.default.so HAL +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/hal-ipc.c \ + bluez/android/hal-bluetooth.c \ + bluez/android/hal-socket.c \ + bluez/android/hal-hidhost.c \ + bluez/android/hal-pan.c \ + bluez/android/hal-a2dp.c \ + bluez/android/hal-avrcp.c \ + bluez/android/hal-handsfree.c \ + bluez/android/hal-gatt.c \ + bluez/android/hal-utils.c \ + bluez/android/hal-health.c \ + +ifeq ($(ANDROID_GE_5_0_0), 1) +LOCAL_SRC_FILES += \ + bluez/android/hal-handsfree-client.c \ + bluez/android/hal-map-client.c \ + bluez/android/hal-a2dp-sink.c \ + bluez/android/hal-avrcp-ctrl.c +endif + +LOCAL_C_INCLUDES += \ + $(call include-path-for, system-core) \ + $(call include-path-for, libhardware) \ + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_MODULE := bluetooth.default +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := SHARED_LIBRARIES +LOCAL_REQUIRED_MODULES := bluetoothd bluetoothd-snoop init.bluetooth.rc + +ifeq ($(ANDROID_GE_5_0_0), 1) +LOCAL_MODULE_RELATIVE_PATH := hw +else +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw +endif + +include $(BUILD_SHARED_LIBRARY) + +# +# haltest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/client/haltest.c \ + bluez/android/client/pollhandler.c \ + bluez/android/client/terminal.c \ + bluez/android/client/history.c \ + bluez/android/client/tabcompletion.c \ + bluez/android/client/if-audio.c \ + bluez/android/client/if-sco.c \ + bluez/android/client/if-av.c \ + bluez/android/client/if-rc.c \ + bluez/android/client/if-bt.c \ + bluez/android/client/if-hf.c \ + bluez/android/client/if-hh.c \ + bluez/android/client/if-pan.c \ + bluez/android/client/if-hl.c \ + bluez/android/client/if-sock.c \ + bluez/android/client/if-gatt.c \ + bluez/android/hal-utils.c \ + +ifeq ($(ANDROID_GE_5_0_0), 1) +LOCAL_SRC_FILES += \ + bluez/android/client/if-hf-client.c \ + bluez/android/client/if-mce.c \ + bluez/android/client/if-av-sink.c \ + bluez/android/client/if-rc-ctrl.c +endif + +LOCAL_C_INCLUDES += \ + $(call include-path-for, system-core) \ + $(call include-path-for, libhardware) \ + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/bluez/android \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) -Wno-declaration-after-statement + +LOCAL_SHARED_LIBRARIES := \ + libhardware \ + libcutils \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := haltest + +include $(BUILD_EXECUTABLE) + +# +# mcaptest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/src/log.c \ + bluez/btio/btio.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + bluez/profiles/health/mcap.c \ + bluez/tools/mcaptest.c \ + +LOCAL_C_INCLUDES := \ + $(call include-path-for, glib) \ + $(call include-path-for, glib)/glib \ + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_SHARED_LIBRARIES := \ + libglib \ + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := mcaptest + +include $(BUILD_EXECUTABLE) + +# +# bneptest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/src/log.c \ + bluez/btio/btio.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + bluez/profiles/network/bnep.c \ + bluez/tools/bneptest.c \ + +LOCAL_C_INCLUDES := \ + $(call include-path-for, glib) \ + $(call include-path-for, glib)/glib \ + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_SHARED_LIBRARIES := \ + libglib \ + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := bneptest + +include $(BUILD_EXECUTABLE) + +# +# avdtptest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/avdtptest.c \ + bluez/android/avdtp.c \ + bluez/src/log.c \ + bluez/btio/btio.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + bluez/src/shared/util.c \ + bluez/src/shared/queue.c \ + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/bluez \ + $(call include-path-for, glib) \ + $(call include-path-for, glib)/glib \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_SHARED_LIBRARIES := \ + libglib \ + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := avdtptest + +include $(BUILD_EXECUTABLE) + +# +# btmon +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/monitor/main.c \ + bluez/monitor/display.c \ + bluez/monitor/hcidump.c \ + bluez/monitor/control.c \ + bluez/monitor/packet.c \ + bluez/monitor/l2cap.c \ + bluez/monitor/avctp.c \ + bluez/monitor/avdtp.c \ + bluez/monitor/a2dp.c \ + bluez/monitor/rfcomm.c \ + bluez/monitor/bnep.c \ + bluez/monitor/uuid.c \ + bluez/monitor/sdp.c \ + bluez/monitor/vendor.c \ + bluez/monitor/lmp.c \ + bluez/monitor/crc.c \ + bluez/monitor/ll.c \ + bluez/monitor/hwdb.c \ + bluez/monitor/keys.c \ + bluez/monitor/ellisys.c \ + bluez/monitor/analyze.c \ + bluez/monitor/intel.c \ + bluez/monitor/broadcom.c \ + bluez/src/shared/util.c \ + bluez/src/shared/queue.c \ + bluez/src/shared/crypto.c \ + bluez/src/shared/btsnoop.c \ + bluez/src/shared/mainloop.c \ + bluez/lib/hci.c \ + bluez/lib/bluetooth.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := btmon + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# btproxy +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/btproxy.c \ + bluez/src/shared/mainloop.c \ + bluez/src/shared/util.c \ + bluez/src/shared/ecc.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := btproxy + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# A2DP audio +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/hal-audio.c \ + bluez/android/hal-audio-sbc.c \ + bluez/android/hal-audio-aptx.c \ + +LOCAL_C_INCLUDES = \ + $(LOCAL_PATH)/bluez \ + $(call include-path-for, system-core) \ + $(call include-path-for, libhardware) \ + $(call include-path-for, sbc) \ + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libsbc \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) -Wno-declaration-after-statement +LOCAL_LDFLAGS := -ldl + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := audio.a2dp.default + +ifeq ($(ANDROID_GE_5_0_0), 1) +LOCAL_MODULE_RELATIVE_PATH := hw +else +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw +endif + +include $(BUILD_SHARED_LIBRARY) + +# +# SCO audio +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := bluez/android/hal-sco.c \ + bluez/android/hal-utils.c + +LOCAL_C_INCLUDES = \ + $(call include-path-for, system-core) \ + $(call include-path-for, libhardware) \ + $(call include-path-for, audio-utils) \ + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libaudioutils \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) -Wno-declaration-after-statement + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := audio.sco.default + +ifeq ($(ANDROID_GE_5_0_0), 1) +LOCAL_MODULE_RELATIVE_PATH := hw +else +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw +endif + +include $(BUILD_SHARED_LIBRARY) + +# +# l2cap-test +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/l2test.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := l2test + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# bluetoothd-snoop +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/bluetoothd-snoop.c \ + bluez/src/shared/mainloop.c \ + bluez/src/shared/btsnoop.c \ + bluez/android/log.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + $(LOCAL_PATH)/bluez/lib \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := bluetoothd-snoop + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# init.bluetooth.rc +# + +include $(CLEAR_VARS) + +LOCAL_MODULE := init.bluetooth.rc +LOCAL_MODULE_CLASS := ETC +LOCAL_SRC_FILES := bluez/android/$(LOCAL_MODULE) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) + +include $(BUILD_PREBUILT) + +# +# btmgmt +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/btmgmt.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + bluez/lib/sdp.c \ + bluez/src/shared/mainloop.c \ + bluez/src/shared/io-mainloop.c \ + bluez/src/shared/mgmt.c \ + bluez/src/shared/queue.c \ + bluez/src/shared/util.c \ + bluez/src/shared/gap.c \ + bluez/src/uuid-helper.c \ + bluez/client/display.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + $(LOCAL_PATH)/bluez/android/compat \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := btmgmt + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# hcitool +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/hcitool.c \ + bluez/src/oui.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := hcitool + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# hciconfig +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + bluez/tools/hciconfig.c \ + bluez/tools/csr.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := hciconfig + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# l2ping +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/l2ping.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := l2ping + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# avtest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/avtest.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := avtest + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# hciattach +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/hciattach.c \ + bluez/tools/hciattach_st.c \ + bluez/tools/hciattach_ti.c \ + bluez/tools/hciattach_tialt.c \ + bluez/tools/hciattach_ath3k.c \ + bluez/tools/hciattach_qualcomm.c \ + bluez/tools/hciattach_intel.c \ + bluez/tools/hciattach_bcm43xx.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := hciattach + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# libsbc +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + sbc/sbc/sbc.c \ + sbc/sbc/sbc_primitives.c \ + sbc/sbc/sbc_primitives_mmx.c \ + sbc/sbc/sbc_primitives_neon.c \ + sbc/sbc/sbc_primitives_armv6.c \ + sbc/sbc/sbc_primitives_iwmmxt.c \ + +LOCAL_C_INCLUDES:= \ + $(LOCAL_PATH)/sbc \ + +LOCAL_CFLAGS:= \ + -Os \ + -Wno-sign-compare \ + -Wno-missing-field-initializers \ + -Wno-unused-parameter \ + -Wno-type-limits \ + -Wno-empty-body \ + +LOCAL_MODULE := libsbc + +include $(BUILD_SHARED_LIBRARY) + +ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) + +# +# bluetoothd (debug) +# this is just a wrapper used in userdebug/eng to launch bluetoothd-main +# with/without Valgrind +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/android/bluetoothd-wrapper.c \ + bluez/android/hal-utils.c + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := bluetoothd + +LOCAL_REQUIRED_MODULES := \ + bluetoothd-main \ + valgrind \ + memcheck-$(TARGET_ARCH)-linux \ + vgpreload_core-$(TARGET_ARCH)-linux \ + vgpreload_memcheck-$(TARGET_ARCH)-linux \ + default.supp + +include $(BUILD_EXECUTABLE) + +endif + +# +# bluetooth-headers +# + +include $(CLEAR_VARS) + +LOCAL_MODULE := bluetooth-headers +LOCAL_NODULE_TAGS := optional +LOCAL_MODULE_CLASS := STATIC_LIBRARIES + +include_path := $(local-intermediates-dir)/include +include_files := $(wildcard $(LOCAL_PATH)/bluez/lib/*.h) +$(shell mkdir -p $(include_path)/bluetooth) +$(foreach file,$(include_files),$(shell cp -u $(file) $(include_path)/bluetooth)) + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(include_path) + +include $(BUILD_STATIC_LIBRARY) + +# +# avtest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/avinfo.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := avinfo + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE) + +# +# rctest +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + bluez/tools/rctest.c \ + bluez/lib/bluetooth.c \ + bluez/lib/hci.c \ + bluez/lib/sdp.c \ + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/bluez \ + +LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS) + +LOCAL_STATIC_LIBRARIES := \ + bluetooth-headers \ + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := rctest + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/bluez/configure.ac + +include $(BUILD_EXECUTABLE)
diff --git a/android/Makefile.am b/android/Makefile.am new file mode 100644 index 0000000..154f8db --- /dev/null +++ b/android/Makefile.am
@@ -0,0 +1,320 @@ +if ANDROID + +AM_CFLAGS += -DANDROID_VERSION=0x050100 + +android_plugindir = $(abs_top_srcdir)/android/.libs + +noinst_PROGRAMS += android/system-emulator + +android_system_emulator_SOURCES = android/system-emulator.c +android_system_emulator_LDADD = src/libshared-mainloop.la + +noinst_PROGRAMS += android/bluetoothd-snoop + +android_bluetoothd_snoop_SOURCES = android/bluetoothd-snoop.c src/log.c +android_bluetoothd_snoop_LDADD = src/libshared-mainloop.la @GLIB_LIBS@ + +noinst_PROGRAMS += android/bluetoothd + +android_bluetoothd_SOURCES = android/main.c \ + src/log.c \ + android/hal-msg.h \ + android/audio-msg.h \ + android/sco-msg.h \ + android/utils.h \ + src/sdpd-database.c src/sdpd-server.c \ + src/sdpd-service.c src/sdpd-request.c \ + src/uuid-helper.h src/uuid-helper.c \ + src/eir.h src/eir.c \ + android/bluetooth.h android/bluetooth.c \ + android/hidhost.h android/hidhost.c \ + profiles/scanparam/scpp.h \ + profiles/scanparam/scpp.c \ + profiles/deviceinfo/dis.h \ + profiles/deviceinfo/dis.c \ + profiles/battery/bas.h profiles/battery/bas.c \ + profiles/input/hog-lib.h \ + profiles/input/hog-lib.c \ + android/ipc-common.h \ + android/ipc.h android/ipc.c \ + android/avdtp.h android/avdtp.c \ + android/a2dp.h android/a2dp.c \ + android/a2dp-sink.h android/a2dp-sink.c \ + android/avctp.h android/avctp.c \ + android/avrcp.h android/avrcp.c \ + android/avrcp-lib.h android/avrcp-lib.c \ + android/socket.h android/socket.c \ + android/sco.h android/sco.c \ + android/pan.h android/pan.c \ + android/handsfree.h android/handsfree.c \ + android/handsfree-client.c android/handsfree-client.h \ + android/gatt.h android/gatt.c \ + android/health.h android/health.c \ + profiles/health/mcap.h profiles/health/mcap.c \ + android/map-client.h android/map-client.c \ + attrib/att.c attrib/att.h \ + attrib/gatt.c attrib/gatt.h \ + attrib/gattrib.c attrib/gattrib.h \ + btio/btio.h btio/btio.c \ + src/sdp-client.h src/sdp-client.c \ + profiles/network/bnep.h profiles/network/bnep.c +android_bluetoothd_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +plugin_LTLIBRARIES += android/bluetooth.default.la + +android_bluetooth_default_la_SOURCES = android/hal.h android/hal-bluetooth.c \ + android/hal-socket.c \ + android/hal-hidhost.c \ + android/hal-health.c \ + android/hal-pan.c \ + android/hal-a2dp.c \ + android/hal-a2dp-sink.c \ + android/hal-avrcp.c \ + android/hal-avrcp-ctrl.c \ + android/hal-handsfree.c \ + android/hal-handsfree-client.c \ + android/hal-gatt.c \ + android/hal-map-client.c \ + android/hardware/bluetooth.h \ + android/hardware/bt_av.h \ + android/hardware/bt_gatt.h \ + android/hardware/bt_gatt_client.h \ + android/hardware/bt_gatt_server.h \ + android/hardware/bt_gatt_types.h \ + android/hardware/bt_hf.h \ + android/hardware/bt_hh.h \ + android/hardware/bt_hl.h \ + android/hardware/bt_pan.h \ + android/hardware/bt_rc.h \ + android/hardware/bt_sock.h \ + android/hardware/bt_hf_client.h \ + android/hardware/bt_mce.h \ + android/hardware/hardware.h \ + android/cutils/properties.h \ + android/ipc-common.h \ + android/hal-log.h \ + android/hal-ipc.h android/hal-ipc.c \ + android/hal-utils.h android/hal-utils.c +android_bluetooth_default_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android +android_bluetooth_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined + +noinst_PROGRAMS += android/avdtptest + +android_avdtptest_SOURCES = android/avdtptest.c \ + src/log.h src/log.c \ + btio/btio.h btio/btio.c \ + src/shared/util.h src/shared/util.c \ + src/shared/queue.h src/shared/queue.c \ + android/avdtp.h android/avdtp.c +android_avdtptest_CFLAGS = $(AM_CFLAGS) +android_avdtptest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ + +noinst_PROGRAMS += android/haltest + +android_haltest_SOURCES = android/client/haltest.c \ + android/client/pollhandler.h \ + android/client/pollhandler.c \ + android/client/terminal.h \ + android/client/terminal.c \ + android/client/history.h \ + android/client/history.c \ + android/client/tabcompletion.c \ + android/client/if-main.h \ + android/client/if-av.c \ + android/client/if-av-sink.c \ + android/client/if-rc.c \ + android/client/if-rc-ctrl.c \ + android/client/if-bt.c \ + android/client/if-gatt.c \ + android/client/if-hf.c \ + android/client/if-hf-client.c \ + android/client/if-hh.c \ + android/client/if-pan.c \ + android/client/if-hl.c \ + android/client/if-sock.c \ + android/client/if-audio.c \ + android/client/if-sco.c \ + android/client/if-mce.c \ + android/hardware/hardware.c \ + android/hal-utils.h android/hal-utils.c +android_haltest_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android \ + -DPLUGINDIR=\""$(android_plugindir)"\" +android_haltest_LDFLAGS = -pthread -ldl -lm + +noinst_PROGRAMS += android/android-tester + +android_android_tester_SOURCES = emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c \ + monitor/rfcomm.h \ + android/hardware/hardware.c \ + android/tester-bluetooth.c \ + android/tester-socket.c \ + android/tester-hidhost.c \ + android/tester-pan.c \ + android/tester-hdp.c \ + android/tester-a2dp.c \ + android/tester-avrcp.c \ + android/tester-gatt.c \ + android/tester-map-client.c \ + android/tester-main.h android/tester-main.c +android_android_tester_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android \ + -DPLUGINDIR=\""$(android_plugindir)"\" +android_android_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ +android_android_tester_LDFLAGS = -pthread -ldl + +noinst_PROGRAMS += android/ipc-tester + +android_ipc_tester_SOURCES = emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c \ + emulator/bthost.h emulator/bthost.c \ + emulator/smp.c \ + android/hal-utils.h android/hal-utils.c \ + android/ipc-common.h android/ipc-tester.c +android_ipc_tester_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android +android_ipc_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la @GLIB_LIBS@ + +plugin_LTLIBRARIES += android/audio.a2dp.default.la + +android_audio_a2dp_default_la_SOURCES = android/audio-msg.h \ + android/hal-msg.h \ + android/hal-audio.h \ + android/hal-audio.c \ + android/hal-audio-sbc.c \ + android/hal-audio-aptx.c \ + android/hardware/audio.h \ + android/hardware/audio_effect.h \ + android/hardware/hardware.h \ + android/system/audio.h +android_audio_a2dp_default_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android \ + @SBC_CFLAGS@ +android_audio_a2dp_default_la_LIBADD = @SBC_LIBS@ +android_audio_a2dp_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined -pthread -lrt + +plugin_LTLIBRARIES += android/audio.sco.default.la + +android_audio_sco_default_la_SOURCES = android/hal-log.h \ + android/sco-msg.h \ + android/hal-sco.c \ + android/hardware/audio.h \ + android/hardware/audio_effect.h \ + android/hardware/hardware.h \ + android/audio_utils/resampler.c \ + android/audio_utils/resampler.h \ + android/system/audio.h +android_audio_sco_default_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android +android_audio_sco_default_la_LIBADD = @SPEEXDSP_LIBS@ +android_audio_sco_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined -lrt +unit_tests += android/test-ipc + +android_test_ipc_SOURCES = android/test-ipc.c \ + src/log.h src/log.c \ + android/ipc-common.h \ + android/ipc.c android/ipc.h +android_test_ipc_LDADD = src/libshared-glib.la @GLIB_LIBS@ + +endif + +EXTRA_DIST += android/Android.mk android/README \ + android/compat/readline/history.h \ + android/compat/readline/readline.h \ + android/compat/wordexp.h \ + android/bluetoothd-wrapper.c \ + android/log.c \ + android/bluetoothd.te \ + android/bluetoothd_snoop.te \ + android/init.bluetooth.rc \ + android/hal-ipc-api.txt \ + android/audio-ipc-api.txt \ + android/cts.txt \ + android/pics-rfcomm.txt \ + android/pics-spp.txt \ + android/pics-sdp.txt \ + android/pics-l2cap.txt \ + android/pics-gap.txt \ + android/pics-did.txt \ + android/pics-hid.txt \ + android/pics-pan.txt \ + android/pics-opp.txt \ + android/pics-map.txt \ + android/pics-pbap.txt \ + android/pics-a2dp.txt \ + android/pics-avctp.txt \ + android/pics-avrcp.txt \ + android/pics-hsp.txt \ + android/pics-hfp.txt \ + android/pics-gatt.txt \ + android/pics-mcap.txt \ + android/pics-hdp.txt \ + android/pics-iopt.txt \ + android/pics-sm.txt \ + android/pics-mps.txt \ + android/pics-hogp.txt \ + android/pics-scpp.txt \ + android/pics-dis.txt \ + android/pics-avdtp.txt \ + android/pics-gavdp.txt \ + android/pics-bnep.txt \ + android/pixit-l2cap.txt \ + android/pixit-gap.txt \ + android/pixit-did.txt \ + android/pixit-hid.txt \ + android/pixit-pan.txt \ + android/pixit-opp.txt \ + android/pixit-map.txt \ + android/pixit-pbap.txt \ + android/pixit-a2dp.txt \ + android/pixit-avctp.txt \ + android/pixit-avrcp.txt \ + android/pixit-hsp.txt \ + android/pixit-hfp.txt \ + android/pixit-gatt.txt \ + android/pixit-mcap.txt \ + android/pixit-hdp.txt \ + android/pixit-iopt.txt \ + android/pixit-sm.txt \ + android/pixit-mps.txt \ + android/pixit-hogp.txt \ + android/pixit-scpp.txt \ + android/pixit-dis.txt \ + android/pixit-rfcomm.txt \ + android/pixit-spp.txt \ + android/pixit-avdtp.txt \ + android/pixit-gavdp.txt \ + android/pixit-sdp.txt \ + android/pixit-bnep.txt \ + android/pts-rfcomm.txt \ + android/pts-spp.txt \ + android/pts-l2cap.txt \ + android/pts-gap.txt \ + android/pts-did.txt \ + android/pts-hid.txt \ + android/pts-pan.txt \ + android/pts-opp.txt \ + android/pts-map.txt \ + android/pts-a2dp.txt \ + android/pts-avrcp.txt \ + android/pts-avctp.txt \ + android/pts-pbap.txt \ + android/pts-hfp.txt \ + android/pts-gatt.txt \ + android/pts-hsp.txt \ + android/pts-iopt.txt \ + android/pts-hdp.txt \ + android/pts-mcap.txt \ + android/pts-mps.txt \ + android/pts-sm.txt \ + android/pts-hogp.txt \ + android/pts-scpp.txt \ + android/pts-dis.txt \ + android/pts-avdtp.txt \ + android/pts-gavdp.txt \ + android/pts-sdp.txt \ + android/pts-bnep.txt
diff --git a/android/README b/android/README new file mode 100644 index 0000000..fa4c42a --- /dev/null +++ b/android/README
@@ -0,0 +1,454 @@ +BlueZ for Android +***************** + +Since Android 4.2 there exists a well standardized HAL interface that the +Bluetooth stack is expected to provide and which enables the easy replacement +of the stack of choice on Android. Android BlueZ is intended as a drop-in +replacement to Android provided Bluetooth stack. + +More details about BlueZ for Android architecture and components can be found +in android/hal-ipc-api.txt file. + +Supported Android version: 4.4 KitKat and 5.0, 5.1 Lollipop + + +Building and running on Android +=============================== + +Steps needed to build and run Android Open Source Project with integrated BlueZ. + + +Build requirements +------------------ + +- GLib - Android 4.2 or later don't provide GLib and one must provide it in +'external/bluetooth/glib' folder of Android tree. Sample Android GLib port +is available at https://github.com/bluez-android/glib + +- SBC - A2DP code requires SBC library (version 1.2 or higher) present in +'external/bluetooth/sbc' directory. Library is build from Android.mk provided +by BlueZ. SBC code is available at git://git.kernel.org/pub/scm/bluetooth/sbc + +- Bionic support - Android 5.0 provides all required functionality. Running +BlueZ on Android 4.4 requires backporting missing features (epoll_create1 and +ppoll calls). Sample Bionic for Android 4.4 with all required features +backported is available at +https://github.com/bluez-android/aosp_platform_bionic + +Runtime requirements +-------------------- + +BlueZ HAL library requires 'bluetoothd' and 'bluetoothd-snoop' services to be +available on Android system. Some permissions settings are also required. + +This can be done by importing init.bluetooth.rc file in init.rc file of targeted +board: +import init.bluetooth.rc + +For convenience examples are provided at: +https://github.com/bluez-android/aosp_device_lge_mako (Nexus 4) +https://github.com/bluez-android/aosp_device_lge_hammerhead (Nexus 5) +https://github.com/bluez-android/aosp_device_asus_flo (Nexus 7 2013) + +Security-Enhanced Linux in Android +---------------------------------- + +Since 5.0 release Android moved to full enforcement of SELinux. This requires +proper policy to be provided for all BlueZ for Android services (and services +interacting with BlueZ). Policies should be placed in external/selinux/ path. + +Required policy files are provided at: +bluetoothd.te +bluetoothd_snoop.te + +For convenience sepolicy.git with all required policies is available at: +https://github.com/bluez-android/aosp_platform_external_sepolicy + +Downloading and building +------------------------ + +Building for Android requires full Android AOSP source tree. Sample Android tree +with all required components present is available at +https://github.com/bluez-android + +This tree provides support for Nexus4 (mako), Nexus 5 (hammerhead) and +Nexus 7 2013 (flo, deb). Tree does not provide binary blobs needed to run +Android on supported devices. Those can be obtained from +https://developers.google.com/android/nexus/drivers. Binary blobs needs to be +unpacked (EULA acceptance required) into 'vendor' directory of Android tree. + +Downloading: +Android 5.0 - 'lollipop' branch +Android 4.4 - 'kitkat' branch + +repo init -u https://github.com/bluez-android/aosp_platform_manifest \ + -b lollipop +repo sync + +Building: +source build/envsetup.sh +lunch aosp_<target>-userdebug +make -j8 + +Flashing: +adb reboot bootloader +fastboot flashall -w + +After full build is done it is possible to rebuild only BlueZ: +'cd external/bluetooth/bluez/android/' +'mm' (or 'mm -B' to force rebuilding of all files) +'adb sync' to update target device. + +Downloading and building for Intel devices +------------------------------------------ + +Sample Android tree with all required components for Intel devices based on +Intel reference image (https://01.org/android-ia) can be reconstructed following +instructions below. + +This tree provides support for Dell XPS12, Minnowboard MAX, Intel NUC, +Acer Iconia W700 and other devices mentioned in: +https://01.org/android-ia/guides/devices + +Downloading: +repo init -u https://github.com/01org/android-bluez-manifest.git -b android-ia \ + -m topic/bluez +repo sync + +Building: +source build/envsetup.sh +lunch haswell_generic-eng +make -j8 + +Installing: +Live and Install image is $OUT/live.img +Flash live.img to USB flash and boot from it. More instructions here: +https://01.org/android-ia/guides/developers/build-and-install + +Linux Kernel requirements +------------------------- + +BlueZ for Android uses Linux Bluetooth subsystem and it must be enabled in +kernel. Minimal required version of management interface is 1.3. This +corresponds to Linux 3.9 but latest available version is recommended. Other +requirements include UHID and network bridge support. + +Following kernel options should be enabled: +CONFIG_BT +CONFIG_BT_RFCOMM +CONFIG_BT_RFCOMM_TTY +CONFIG_BT_BNEP +CONFIG_BT_BNEP_MC_FILTER +CONFIG_BT_BNEP_PROTO_FILTER +CONFIG_BRIDGE +CONFIG_UHID +CONFIG_CRYPTO_CMAC +CONFIG_CRYPTO_USER_API +CONFIG_CRYPTO_USER_API_HASH +CONFIG_CRYPTO_USER_API_SKCIPHER + +Also BT chip driver needs to be enabled e.g: +CONFIG_BT_HCIBTUSB + +If it is not possible to use new enough Linux kernel one can use updated +bluetooth subsystem from Backports project. More information about Backports can +be found at https://backports.wiki.kernel.org. Sample kernels using backports +for running BlueZ on Android are available at https://github.com/bluez-android. + + +Running with Valgrind +--------------------- + +BlueZ for Android is preconfigured to be easily run under Valgrind memcheck. +Appropriate configuration and required modules are automatically included when +building either userdebug or eng variant of Android platform. + +Valgrind can be enabled in runtime by setting "persist.sys.bluetooth.valgrind" +property to either literal "true" or any numeric value >0. For example: +adb root +adb shell setprop persist.sys.bluetooth.valgrind true + +After changing property value Bluetooth need to be restarted to apply changes +(this can be done using UI, just disable and enable it again). Property is +persistent, i.e. there's no need to enable Valgrind again after reboot. + +It's recommended to have unstripped libglib.so installed which will enable +complete backtraces in Valgrind output. Otherwise, in many cases backtrace +will break at e.g. g_free() function without prior callers. It's possible to +have proper library installed automatically by appropriate entry in Android.mk, +see https://github.com/bluez-android/glib for an example. + +When running with valgrind SElinux needs to be set into permissive mode. This +can be done by executing 'setenforce 0' from root shell. + + +Enabling BlueZ debugs +--------------------- + +BlueZ debug logs can be enabled in runtime by setting +"persist.sys.bluetooth.debug" property to either literal "true" or any +numeric value >0. For example: +adb root +adb shell setprop persist.sys.bluetooth.debug 1 + +After changing property value Bluetooth needs to be restarted to apply changes. + +There is also a possibility to enable mgmt debug logs which also enables debugs +as above. To enable it proceed in the same way as described above but use +system properties called: persist.sys.bluetooth.mgmtdbg + +Note: Debugs are only available on NON USER build variants + + +Customization +------------- + +It is possible to customize BlueZ for Android through Android system properties. +This may include enabling extra profiles or features inside HALs implementation +These properties are read on Bluetooth stack startup only and require stack +restart if changed. All customization properties names start with +"persist.sys.bluetooth." or "ro.bluetooth." followed by specific HAL name e.g. +"persist.sys.bluetooth.handsfree". If both are present "persist.sys.bluetooth." +takes precedence. This allows for read only properties to be set during build +leaving enough flexibility for developing or debugging purposes. +This section list available customization options. + +Property Value Description +------------------------------------------- +mode bredr Enable BlueZ in BR/EDR mode + le Enable BlueZ in LE mode + <none> Enable BlueZ in default mode - enable BR/EDR/LE + if available. +handsfree hfp Enable Handsfree Profile (HFP) with narrowband + speech only + hfp_wbs Enable Handsfree Profile (HFP) with narrowband + and wideband speech support + <none> Don't enable Handsfree Profile (HFP) +vendor <any> Set vendor name in DIS. If not set fallback to + "ro.product.manufacturer". +model <any> Set model name used as default adapter name. + If not set fallback to "ro.product.model". +name <any> Set model number in DIS. If not set fallback to + "ro.product.name". +serialno <any> Set serial number in DIS. If not set fallback to + "ro.serialno". +systemid <uint64> Set system ID in DIS. Hex string encoded uint64. +pnpid <any> PnP information used in DIS and DID profiles. + Required format: "Source:VID:PID:Version". + Source must be either "bluetooth" or "usb". + VID, PID and Version are uint16. Version is + optional. +fwrev <any> Firmware revision in DIS. If not set fallback to + "ro.build.version.release". +hwrew <any> Hardware revision in DIS. If not set fallback to + "ro.board.platform". + + +Building and running on Linux +----------------------------- + +It is possible to build and test BlueZ for Android daemon on Linux (eg. PC). +Simply follow instructions available at README file in BlueZ top directory. +Android daemon binary is located at android/bluetoothd. See next section on +how to test Android daemon on Linux. + + +Testing tool +------------ + +BT HAL test tools located in android/haltest is provided for HAL level testing +of both Android daemon and HAL library. Start it with '-n' parameter and type +'bluetooth init' in prompt to initialize HAL library. Running without parameter +will make haltest try to initialize all services after start. On Android +required bluetoothd service will be started automatically. On Linux it is +required to start android/bluetoothd manually before init command timeout or +use provided android/system-emulator, which takes care of launching daemon +automatically on HAL library initialization. To deinitialize HAL library and +stop daemon type 'bluetooth cleanup'. Type 'help' for more information. Tab +completion is also supported. + + +Implementation status +===================== + +Summary of HALs implementation status. + +complete - implementation is feature complete and Android Framework is able + to use it normally +partial - implementation is in progress and not all required features are + present, Android Framework is able to use some of features +initial - only initial implementations is present, Android Framework is + able to initialize but most likely not able to use it +not started - no implementation, Android Framework is not able to initialize it + +Profile ID HAL header 4.4 Status 5.0 status +------------------------------------------------------------- +core bluetooth.h complete partial +a2dp bt_av.h complete complete +gatt bt_gatt.h complete partial + bt_gatt_client.h complete partial + bt_gatt_server.h complete partial +handsfree bt_hf.h complete complete +hidhost bt_hh.h complete complete +health bt_hl.h complete complete +pan bt_pan.h complete complete +avrcp bt_rc.h complete complete +socket bt_sock.h complete partial +handsfree_client bt_hf_client.h N/A complete +map_client bt_mce.h N/A complete +a2dp_sink bt_av.h N/A partial +avrcp_ctrl bt_rc.h N/A partial + + +Implementation shortcomings +=========================== + +It is possible that some of HAL functionality (although being marked as +complete) is missing implementation due to reasons like feature feasibility or +necessity for latest Android Framework. This sections provides list of such +deficiencies. Note that HAL library is always expected to fully implement HAL +API so missing implementation might happen only in daemon. + + +HAL Bluetooth +------------- + +methods: +dut_mode_send never called from Android Framework +le_test_mode never called from Android Framework + +callbacks: +dut_mode_recv_cb empty JNI implementation +le_test_mode_cb empty JNI implementation + +properties: +BT_PROPERTY_SERVICE_RECORD not supported for adapter, for device this + property is returned as a response to + get_remote_service_record call + +BT_PROPERTY_REMOTE_VERSION_INFO information required by this property (LMP + information) are not accessible from mgmt + interface, also marking this property as + settable is probably a typo in HAL header + +HAL Socket +---------- + +Support only for BTSOCK_RFCOMM socket type. + + +HAL AVRCP +--------- + +methods: +list_player_app_attr_rsp never called from Android Framework +list_player_app_value_rsp never called from Android Framework +get_player_app_value_rsp never called from Android Framework +get_player_app_attr_text_rsp never called from Android Framework +get_player_app_value_text_rsp never called from Android Framework +set_player_app_value_rsp never called from Android Framework + +callbacks: +list_player_app_attr_cb NULL JNI implementation +list_player_app_values_cb NULL JNI implementation +get_player_app_value_cb NULL JNI implementation +get_player_app_attrs_text_cb NULL JNI implementation +get_player_app_values_text_cb NULL JNI implementation +set_player_app_value_cb NULL JNI implementation + + +HAL GATT +-------- + +methods: +client->set_adv_data missing kernel support for vendor data +client->connect is_direct parameter is ignored + + +Audio SCO HAL +============= + +When Bluetooth chip's audio is not wired directly to device audio, Audio SCO +HAL is used to enable SCO support. It needs to be loaded by AudioFlinger +following audio_policy.conf configuration. Example of configuration is shown +below: + +... + sco { + outputs { + sco { + sampling_rates 8000|44100 + channel_masks AUDIO_CHANNEL_OUT_STEREO + formats AUDIO_FORMAT_PCM_16_BIT + devices AUDIO_DEVICE_OUT_ALL_SCO + } + } + inputs { + sco { + sampling_rates 8000|44100 + channel_masks AUDIO_CHANNEL_IN_MONO + formats AUDIO_FORMAT_PCM_16_BIT + devices AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET + } + } + } +... + +Known Android issues +==================== + +It is possible that BlueZ is triggering bugs on Android Framework that could +affect qualification or user experience. This section provides list of +recommended Android fixes that are not part of latest AOSP release supported by +BlueZ. + +For Android 5.1 Lollipop: +https://android-review.googlesource.com/177314 + +For Android 5.0 Lollipop: +https://android-review.googlesource.com/99761 +https://android-review.googlesource.com/100297 +https://android-review.googlesource.com/102882 +https://android-review.googlesource.com/132733 +https://android-review.googlesource.com/132763 +https://android-review.googlesource.com/177314 + +For Android 4.4 KitKat: +https://android-review.googlesource.com/82757 +https://android-review.googlesource.com/87670 +https://android-review.googlesource.com/88384 +https://android-review.googlesource.com/99761 +https://android-review.googlesource.com/99850 +https://android-review.googlesource.com/100297 +https://android-review.googlesource.com/102882 +https://android-review.googlesource.com/177314 + +Unimplemented Bluetooth features +================================ + +Some Bluetooth functionality require support from outside of BT stack +eg. telephony stack. This sections describes profiles optional features not +implemented due to lack of support in other Android subsystems or missing API +in respective BT HALs. + +Profile Feature Comments +-------------------------------------------------------- +HFP Attach a phone number to AT+BINP=1 + a voice tag +HFP Enhanced Call Control AT+CHLD={1x,2x} +HFP Explicit Call Transfer AT+CHLD=4 +HFP Response and Hold AT+BTRH, +BTRH +HFP In-band Ring Tone +BSIR +AVRCP Player Settings HAL API present but not used +AVRCP Browsing No HAL API +GATT Read multiple characteristics No HAL API + + +Reporting Bugs +============== + +Bugs should be reported at https://01.org/jira/browse/BA. When reporting +a bug please attach logs from logcat (logcat -v time) and HCI trace. Daemon +debug logs should be enabled. When reporting daemon crash please run it under +valgrind if possible. For details on how to enabled debug logs and valgrind see +"Enabling BlueZ debugs" section.
diff --git a/android/a2dp-sink.c b/android/a2dp-sink.c new file mode 100644 index 0000000..7c1e1a0 --- /dev/null +++ b/android/a2dp-sink.c
@@ -0,0 +1,84 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <glib.h> + +#include "lib/bluetooth.h" +#include "src/log.h" +#include "hal-msg.h" +#include "ipc.h" +#include "a2dp-sink.h" + +static struct ipc *hal_ipc = NULL; + +static void bt_a2dp_sink_connect(const void *buf, uint16_t len) +{ + /* TODO */ + + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_A2DP_SINK, HAL_OP_A2DP_CONNECT, + HAL_STATUS_UNSUPPORTED); +} + +static void bt_a2dp_sink_disconnect(const void *buf, uint16_t len) +{ + /* TODO */ + + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_A2DP_SINK, HAL_OP_A2DP_DISCONNECT, + HAL_STATUS_UNSUPPORTED); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_A2DP_CONNECT */ + { bt_a2dp_sink_connect, false, sizeof(struct hal_cmd_a2dp_connect) }, + /* HAL_OP_A2DP_DISCONNECT */ + { bt_a2dp_sink_disconnect, false, + sizeof(struct hal_cmd_a2dp_disconnect) }, +}; + +bool bt_a2dp_sink_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + DBG(""); + + hal_ipc = ipc; + ipc_register(hal_ipc, HAL_SERVICE_ID_A2DP_SINK, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; +} + +void bt_a2dp_sink_unregister(void) +{ + DBG(""); + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_A2DP_SINK); + hal_ipc = NULL; +}
diff --git a/android/a2dp-sink.h b/android/a2dp-sink.h new file mode 100644 index 0000000..d2c5ff4 --- /dev/null +++ b/android/a2dp-sink.h
@@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_a2dp_sink_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_a2dp_sink_unregister(void);
diff --git a/android/a2dp.c b/android/a2dp.c new file mode 100644 index 0000000..f219042 --- /dev/null +++ b/android/a2dp.c
@@ -0,0 +1,1774 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <glib.h> + +#include "btio/btio.h" +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "profiles/audio/a2dp-codecs.h" +#include "src/shared/queue.h" +#include "src/log.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "a2dp.h" +#include "utils.h" +#include "bluetooth.h" +#include "avdtp.h" +#include "avrcp.h" +#include "audio-msg.h" + +#define SVC_HINT_CAPTURING 0x08 +#define IDLE_TIMEOUT 1 +#define AUDIO_RETRY_TIMEOUT 2 + +static GIOChannel *server = NULL; +static GSList *devices = NULL; +static GSList *endpoints = NULL; +static GSList *setups = NULL; +static bdaddr_t adapter_addr; +static uint32_t record_id = 0; +static guint audio_retry_id = 0; +static bool audio_retrying = false; + +static struct ipc *hal_ipc = NULL; +static struct ipc *audio_ipc = NULL; + +static struct queue *lseps = NULL; + +struct a2dp_preset { + void *data; + int8_t len; +}; + +struct a2dp_endpoint { + uint8_t id; + uint8_t codec; + struct avdtp_local_sep *sep; + struct a2dp_preset *caps; + GSList *presets; +}; + +struct a2dp_device { + bdaddr_t dst; + uint8_t state; + GIOChannel *io; + struct avdtp *session; + guint idle_id; +}; + +struct a2dp_setup { + struct a2dp_device *dev; + struct a2dp_endpoint *endpoint; + struct a2dp_preset *preset; + struct avdtp_stream *stream; + uint8_t state; +}; + +static int device_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct a2dp_device *dev = s; + const bdaddr_t *dst = user_data; + + return bacmp(&dev->dst, dst); +} + +static void preset_free(void *data) +{ + struct a2dp_preset *preset = data; + + g_free(preset->data); + g_free(preset); +} + +static void unregister_endpoint(void *data) +{ + struct a2dp_endpoint *endpoint = data; + + if (endpoint->sep) + avdtp_unregister_sep(lseps, endpoint->sep); + + if (endpoint->caps) + preset_free(endpoint->caps); + + g_slist_free_full(endpoint->presets, preset_free); + + g_free(endpoint); +} + +static void setup_free(void *data) +{ + struct a2dp_setup *setup = data; + + if (!g_slist_find(setup->endpoint->presets, setup->preset)) + preset_free(setup->preset); + + g_free(setup); +} + +static void setup_remove(struct a2dp_setup *setup) +{ + setups = g_slist_remove(setups, setup); + setup_free(setup); +} + +static void setup_remove_all_by_dev(struct a2dp_device *dev) +{ + GSList *l = setups; + + while (l) { + struct a2dp_setup *setup = l->data; + GSList *next = g_slist_next(l); + + if (setup->dev == dev) + setup_remove(setup); + + l = next; + } +} + +static void a2dp_device_free(void *data) +{ + struct a2dp_device *dev = data; + + if (dev->idle_id > 0) + g_source_remove(dev->idle_id); + + if (dev->session) + avdtp_unref(dev->session); + + if (dev->io) { + g_io_channel_shutdown(dev->io, FALSE, NULL); + g_io_channel_unref(dev->io); + } + + setup_remove_all_by_dev(dev); + + g_free(dev); +} + +static void a2dp_device_remove(struct a2dp_device *dev) +{ + devices = g_slist_remove(devices, dev); + a2dp_device_free(dev); +} + +static struct a2dp_device *a2dp_device_new(const bdaddr_t *dst) +{ + struct a2dp_device *dev; + + dev = g_new0(struct a2dp_device, 1); + bacpy(&dev->dst, dst); + devices = g_slist_prepend(devices, dev); + + return dev; +} + +static bool a2dp_device_connect(struct a2dp_device *dev, BtIOConnect cb) +{ + GError *err = NULL; + + dev->io = bt_io_connect(cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + return false; + } + + return true; +} + +static void bt_a2dp_notify_state(struct a2dp_device *dev, uint8_t state) +{ + struct hal_ev_a2dp_conn_state ev; + char address[18]; + + if (dev->state == state) + return; + + dev->state = state; + + ba2str(&dev->dst, address); + DBG("device %s state %u", address, state); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_EV_A2DP_CONN_STATE, + sizeof(ev), &ev); + + if (state != HAL_A2DP_STATE_DISCONNECTED) + return; + + bt_avrcp_disconnect(&dev->dst); + + a2dp_device_remove(dev); +} + +static void bt_audio_notify_state(struct a2dp_setup *setup, uint8_t state) +{ + struct hal_ev_a2dp_audio_state ev; + char address[18]; + + if (setup->state == state) + return; + + setup->state = state; + + ba2str(&setup->dev->dst, address); + DBG("device %s state %u", address, state); + + bdaddr2android(&setup->dev->dst, ev.bdaddr); + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_EV_A2DP_AUDIO_STATE, + sizeof(ev), &ev); +} + +static void disconnect_cb(void *user_data) +{ + struct a2dp_device *dev = user_data; + + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); +} + +static int sbc_check_config(void *caps, uint8_t caps_len, void *conf, + uint8_t conf_len) +{ + a2dp_sbc_t *cap, *config; + + if (conf_len != caps_len || conf_len != sizeof(a2dp_sbc_t)) { + error("SBC: Invalid configuration size (%u)", conf_len); + return -EINVAL; + } + + cap = caps; + config = conf; + + if (!(cap->frequency & config->frequency)) { + error("SBC: Unsupported frequency (%u) by endpoint", + config->frequency); + return -EINVAL; + } + + if (!(cap->channel_mode & config->channel_mode)) { + error("SBC: Unsupported channel mode (%u) by endpoint", + config->channel_mode); + return -EINVAL; + } + + if (!(cap->block_length & config->block_length)) { + error("SBC: Unsupported block length (%u) by endpoint", + config->block_length); + return -EINVAL; + } + + if (!(cap->allocation_method & config->allocation_method)) { + error("SBC: Unsupported allocation method (%u) by endpoint", + config->block_length); + return -EINVAL; + } + + if (config->max_bitpool < cap->min_bitpool) { + error("SBC: Invalid maximun bitpool (%u < %u)", + config->max_bitpool, cap->min_bitpool); + return -EINVAL; + } + + if (config->min_bitpool > cap->max_bitpool) { + error("SBC: Invalid minimun bitpool (%u > %u)", + config->min_bitpool, cap->min_bitpool); + return -EINVAL; + } + + if (config->max_bitpool > cap->max_bitpool) + return -ERANGE; + + if (config->min_bitpool < cap->min_bitpool) + return -ERANGE; + + return 0; +} + +static int aac_check_config(void *caps, uint8_t caps_len, void *conf, + uint8_t conf_len) +{ + a2dp_aac_t *cap, *config; + + if (conf_len != caps_len || conf_len != sizeof(a2dp_aac_t)) { + error("AAC: Invalid configuration size (%u)", conf_len); + return -EINVAL; + } + + cap = caps; + config = conf; + + if (!(cap->object_type & config->object_type)) { + error("AAC: Unsupported object type (%u) by endpoint", + config->object_type); + return -EINVAL; + } + + if (!(AAC_GET_FREQUENCY(*cap) & AAC_GET_FREQUENCY(*config))) { + error("AAC: Unsupported frequency (%u) by endpoint", + AAC_GET_FREQUENCY(*config)); + return -EINVAL; + } + + if (!(cap->channels & config->channels)) { + error("AAC: Unsupported channels (%u) by endpoint", + config->channels); + return -EINVAL; + } + + /* VBR support in SNK is mandatory but let's make sure we don't try to + * have VBR on remote which for some reason does not support it + */ + if (!cap->vbr && config->vbr) { + error("AAC: Unsupported VBR (%u) by endpoint", + config->vbr); + return -EINVAL; + } + + if (AAC_GET_BITRATE(*cap) < AAC_GET_BITRATE(*config)) + return -ERANGE; + + return 0; +} + +static int aptx_check_config(void *caps, uint8_t caps_len, void *conf, + uint8_t conf_len) +{ + a2dp_aptx_t *cap, *config; + + if (conf_len != caps_len || conf_len != sizeof(a2dp_aptx_t)) { + error("APTX: Invalid configuration size (%u)", conf_len); + return -EINVAL; + } + + cap = caps; + config = conf; + + if (!(cap->frequency & config->frequency)) { + error("APTX: Unsupported frequenct (%u) by endpoint", + config->frequency); + return -EINVAL; + } + + if (!(cap->channel_mode & config->channel_mode)) { + error("APTX: Unsupported channel mode (%u) by endpoint", + config->channel_mode); + return -EINVAL; + } + + return 0; +} + +static int check_capabilities(struct a2dp_preset *preset, + struct avdtp_media_codec_capability *codec, + uint8_t codec_len) +{ + a2dp_vendor_codec_t *vndcodec; + + /* Codec specific */ + switch (codec->media_codec_type) { + case A2DP_CODEC_SBC: + return sbc_check_config(codec->data, codec_len, preset->data, + preset->len); + case A2DP_CODEC_MPEG24: + return aac_check_config(codec->data, codec_len, preset->data, + preset->len); + case A2DP_CODEC_VENDOR: + vndcodec = (void *) codec->data; + if (btohl(vndcodec->vendor_id) == APTX_VENDOR_ID && + btohs(vndcodec->codec_id) == APTX_CODEC_ID) + return aptx_check_config(codec->data, codec_len, + preset->data, preset->len); + return -EINVAL; + default: + return -EINVAL; + } +} + +static struct a2dp_preset *sbc_select_range(void *caps, uint8_t caps_len, + void *conf, uint8_t conf_len) +{ + struct a2dp_preset *p; + a2dp_sbc_t *cap, *config; + + cap = caps; + config = conf; + + config->min_bitpool = MAX(config->min_bitpool, cap->min_bitpool); + config->max_bitpool = MIN(config->max_bitpool, cap->max_bitpool); + + p = g_new0(struct a2dp_preset, 1); + p->len = conf_len; + p->data = g_memdup(conf, p->len); + + return p; +} + +static struct a2dp_preset *aac_select_range(void *caps, uint8_t caps_len, + void *conf, uint8_t conf_len) +{ + struct a2dp_preset *p; + a2dp_aac_t *cap, *config; + uint32_t bitrate; + + cap = caps; + config = conf; + + bitrate = MIN(AAC_GET_BITRATE(*cap), AAC_GET_BITRATE(*config)); + AAC_SET_BITRATE(*config, bitrate); + + p = g_new0(struct a2dp_preset, 1); + p->len = conf_len; + p->data = g_memdup(conf, p->len); + + return p; +} + +static struct a2dp_preset *select_preset_range(struct a2dp_preset *preset, + struct avdtp_media_codec_capability *codec, + uint8_t codec_len) +{ + /* Codec specific */ + switch (codec->media_codec_type) { + case A2DP_CODEC_SBC: + return sbc_select_range(codec->data, codec_len, preset->data, + preset->len); + case A2DP_CODEC_MPEG24: + return aac_select_range(codec->data, codec_len, preset->data, + preset->len); + default: + return NULL; + } +} + +static struct a2dp_preset *select_preset(struct a2dp_endpoint *endpoint, + struct avdtp_remote_sep *rsep) +{ + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + GSList *l; + uint8_t codec_len; + + service = avdtp_get_codec(rsep); + codec = (struct avdtp_media_codec_capability *) service->data; + codec_len = service->length - sizeof(*codec); + + for (l = endpoint->presets; l; l = g_slist_next(l)) { + struct a2dp_preset *preset = l->data; + int err; + + err = check_capabilities(preset, codec, codec_len); + if (err == 0) + return preset; + + if (err == -ERANGE) + return select_preset_range(preset, codec, codec_len); + } + + return NULL; +} + +static void setup_add(struct a2dp_device *dev, struct a2dp_endpoint *endpoint, + struct a2dp_preset *preset, struct avdtp_stream *stream) +{ + struct a2dp_setup *setup; + + setup = g_new0(struct a2dp_setup, 1); + setup->dev = dev; + setup->endpoint = endpoint; + setup->preset = preset; + setup->stream = stream; + setups = g_slist_append(setups, setup); + + if (dev->idle_id > 0) { + g_source_remove(dev->idle_id); + dev->idle_id = 0; + } +} + +static int select_configuration(struct a2dp_device *dev, + struct a2dp_endpoint *endpoint, + struct avdtp_remote_sep *rsep) +{ + struct a2dp_preset *preset; + struct avdtp_stream *stream; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + GSList *caps; + int err; + + preset = select_preset(endpoint, rsep); + if (!preset) { + error("Unable to select codec preset"); + return -EINVAL; + } + + service = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); + caps = g_slist_append(NULL, service); + + codec = g_malloc0(sizeof(*codec) + preset->len); + codec->media_type = AVDTP_MEDIA_TYPE_AUDIO; + codec->media_codec_type = endpoint->codec; + memcpy(codec->data, preset->data, preset->len); + + service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec, + sizeof(*codec) + preset->len); + caps = g_slist_append(caps, service); + + g_free(codec); + + err = avdtp_set_configuration(dev->session, rsep, endpoint->sep, caps, + &stream); + g_slist_free_full(caps, g_free); + if (err < 0) { + error("avdtp_set_configuration: %s", strerror(-err)); + return err; + } + + setup_add(dev, endpoint, preset, stream); + + return 0; +} + +static void discover_cb(struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_device *dev = user_data; + struct a2dp_endpoint *endpoint = NULL; + struct avdtp_remote_sep *rsep = NULL; + GSList *l; + + for (l = endpoints; l; l = g_slist_next(l)) { + endpoint = l->data; + + rsep = avdtp_find_remote_sep(session, endpoint->sep); + if (rsep) + break; + } + + if (!rsep) { + error("Unable to find matching endpoint"); + goto failed; + } + + if (select_configuration(dev, endpoint, rsep) < 0) + goto failed; + + return; + +failed: + avdtp_shutdown(session); +} + +static gboolean idle_timeout(gpointer user_data) +{ + struct a2dp_device *dev = user_data; + int err; + + dev->idle_id = 0; + + err = avdtp_discover(dev->session, discover_cb, dev); + if (err == 0) + return FALSE; + + error("avdtp_discover: %s", strerror(-err)); + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); + + return FALSE; +} + +static void signaling_connect_cb(GIOChannel *chan, GError *err, + gpointer user_data) +{ + struct a2dp_device *dev = user_data; + struct avdtp *session; + uint16_t imtu, omtu; + GError *gerr = NULL; + int fd; + + if (err) { + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); + error("%s", err->message); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + goto failed; + } + + fd = g_io_channel_unix_get_fd(chan); + + /* FIXME: Add proper version */ + session = avdtp_new(fd, imtu, omtu, 0x0100, lseps); + if (!session) + goto failed; + + dev->session = session; + + avdtp_add_disconnect_cb(dev->session, disconnect_cb, dev); + + /* Proceed to stream setup if initiator */ + if (dev->io) { + int perr; + + g_io_channel_unref(dev->io); + dev->io = NULL; + + perr = avdtp_discover(dev->session, discover_cb, dev); + if (perr < 0) { + error("avdtp_discover: %s", strerror(-perr)); + goto failed; + } + bt_avrcp_connect(&dev->dst); + } else /* Init idle timeout to discover */ + dev->idle_id = g_timeout_add_seconds(IDLE_TIMEOUT, idle_timeout, + dev); + + return; + +failed: + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); +} + +static void bt_a2dp_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_a2dp_connect *cmd = buf; + struct a2dp_device *dev; + uint8_t status; + char addr[18]; + bdaddr_t dst; + GSList *l; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = a2dp_device_new(&dst); + if (!a2dp_device_connect(dev, signaling_connect_cb)) { + a2dp_device_remove(dev); + status = HAL_STATUS_FAILED; + goto failed; + } + + ba2str(&dev->dst, addr); + DBG("connecting to %s", addr); + + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_CONNECTING); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_OP_A2DP_CONNECT, status); +} + +static void bt_a2dp_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_a2dp_connect *cmd = buf; + uint8_t status; + struct a2dp_device *dev; + GSList *l; + bdaddr_t dst; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + status = HAL_STATUS_SUCCESS; + + if (dev->io) { + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTED); + goto failed; + } + + /* Wait AVDTP session to shutdown */ + avdtp_shutdown(dev->session); + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_DISCONNECTING); + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_A2DP, HAL_OP_A2DP_DISCONNECT, + status); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_A2DP_CONNECT */ + { bt_a2dp_connect, false, sizeof(struct hal_cmd_a2dp_connect) }, + /* HAL_OP_A2DP_DISCONNECT */ + { bt_a2dp_disconnect, false, sizeof(struct hal_cmd_a2dp_disconnect) }, +}; + +static struct a2dp_setup *find_setup_by_device(struct a2dp_device *dev) +{ + GSList *l; + + for (l = setups; l; l = g_slist_next(l)) { + struct a2dp_setup *setup = l->data; + + if (setup->dev == dev) + return setup; + } + + return NULL; +} + +static void transport_connect_cb(GIOChannel *chan, GError *err, + gpointer user_data) +{ + struct a2dp_device *dev = user_data; + struct a2dp_setup *setup; + uint16_t imtu, omtu; + GError *gerr = NULL; + int fd; + + if (err) { + error("%s", err->message); + return; + } + + setup = find_setup_by_device(dev); + if (!setup) { + error("Unable to find stream setup"); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + return; + } + + fd = g_io_channel_unix_get_fd(chan); + + if (!avdtp_stream_set_transport(setup->stream, fd, imtu, omtu)) { + error("avdtp_stream_set_transport: failed"); + return; + } + + g_io_channel_set_close_on_unref(chan, FALSE); + + if (dev->io) { + g_io_channel_unref(dev->io); + dev->io = NULL; + } + + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_CONNECTED); +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct a2dp_device *dev; + bdaddr_t dst; + char address[18]; + GError *gerr = NULL; + GSList *l; + + if (err) { + error("%s", err->message); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + ba2str(&dst, address); + DBG("Incoming connection from %s", address); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) { + transport_connect_cb(chan, err, l->data); + return; + } + + dev = a2dp_device_new(&dst); + bt_a2dp_notify_state(dev, HAL_A2DP_STATE_CONNECTING); + signaling_connect_cb(chan, err, dev); +} + +static sdp_record_t *a2dp_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVDTP_UUID; + uint16_t a2dp_ver = 0x0103, avdtp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &a2dp_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); + profile[0].version = a2dp_ver; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + 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(&avdtp_uuid, AVDTP_UUID); + proto[1] = sdp_list_append(NULL, &avdtp_uuid); + version = sdp_data_alloc(SDP_UINT16, &avdtp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, 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, "Audio Source", NULL, NULL); + + sdp_data_free(psm); + sdp_data_free(version); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static gboolean sep_getcap_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_preset *cap = endpoint->caps; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + + *caps = NULL; + + service = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); + *caps = g_slist_append(*caps, service); + + codec = g_malloc0(sizeof(*codec) + cap->len); + codec->media_type = AVDTP_MEDIA_TYPE_AUDIO; + codec->media_codec_type = endpoint->codec; + memcpy(codec->data, cap->data, cap->len); + + service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec, + sizeof(*codec) + cap->len); + *caps = g_slist_append(*caps, service); + g_free(codec); + + return TRUE; +} + +static int check_config(struct a2dp_endpoint *endpoint, + struct a2dp_preset *config) +{ + GSList *l; + struct a2dp_preset *caps; + + for (l = endpoint->presets; l; l = g_slist_next(l)) { + struct a2dp_preset *preset = l->data; + + if (preset->len != config->len) + continue; + + if (memcmp(preset->data, config->data, preset->len) == 0) + return 0; + } + + caps = endpoint->caps; + + /* Codec specific */ + switch (endpoint->codec) { + case A2DP_CODEC_SBC: + return sbc_check_config(caps->data, caps->len, config->data, + config->len); + default: + return -EINVAL; + } +} + +static struct a2dp_device *find_device_by_session(struct avdtp *session) +{ + GSList *l; + + for (l = devices; l; l = g_slist_next(l)) { + struct a2dp_device *dev = l->data; + + if (dev->session == session) + return dev; + } + + return NULL; +} + +static struct a2dp_setup *find_setup(uint8_t id) +{ + GSList *l; + + for (l = setups; l; l = g_slist_next(l)) { + struct a2dp_setup *setup = l->data; + + if (setup->endpoint->id == id) + return setup; + } + + return NULL; +} + +static void setup_remove_by_id(uint8_t id) +{ + struct a2dp_setup *setup; + + setup = find_setup(id); + if (!setup) { + error("Unable to find stream setup for endpoint %u", id); + return; + } + + setup_remove(setup); +} + +static gboolean sep_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_device *dev; + struct a2dp_preset *preset = NULL; + + DBG(""); + + dev = find_device_by_session(session); + if (!dev) { + error("Unable to find device for session %p", session); + return FALSE; + } + + for (; caps != NULL; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + struct avdtp_media_codec_capability *codec; + + if (cap->category == AVDTP_DELAY_REPORTING) + return FALSE; + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec = (struct avdtp_media_codec_capability *) cap->data; + + if (codec->media_codec_type != endpoint->codec) + return FALSE; + + preset = g_new0(struct a2dp_preset, 1); + preset->len = cap->length - sizeof(*codec); + preset->data = g_memdup(codec->data, preset->len); + + if (check_config(endpoint, preset) < 0) { + preset_free(preset); + return FALSE; + } + } + + if (!preset) + return FALSE; + + setup_add(dev, endpoint, preset, stream); + + cb(session, stream, NULL); + + return TRUE; +} + +static gboolean sep_open_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for endpoint %u", + endpoint->id); + *err = AVDTP_SEP_NOT_IN_USE; + return FALSE; + } + + return TRUE; +} + +static gboolean sep_close_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + uint8_t *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for endpoint %u", + endpoint->id); + *err = AVDTP_SEP_NOT_IN_USE; + return FALSE; + } + + bt_audio_notify_state(setup, HAL_AUDIO_STOPPED); + + setup_remove(setup); + + return TRUE; +} + +static gboolean sep_start_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + uint8_t *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for endpoint %u", + endpoint->id); + *err = AVDTP_SEP_NOT_IN_USE; + return FALSE; + } + + bt_audio_notify_state(setup, HAL_AUDIO_STARTED); + + return TRUE; +} + +static gboolean sep_suspend_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + uint8_t *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for endpoint %u", + endpoint->id); + *err = AVDTP_SEP_NOT_IN_USE; + return FALSE; + } + + bt_audio_notify_state(setup, HAL_AUDIO_SUSPEND); + + return TRUE; +} + +static struct avdtp_sep_ind sep_ind = { + .get_capability = sep_getcap_ind, + .set_configuration = sep_setconf_ind, + .open = sep_open_ind, + .close = sep_close_ind, + .start = sep_start_ind, + .suspend = sep_suspend_ind, +}; + +static void sep_setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + int ret; + + DBG(""); + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for endpoint %u", + endpoint->id); + return; + } + + if (err) + goto failed; + + ret = avdtp_open(session, stream); + if (ret < 0) { + error("avdtp_open: %s", strerror(-ret)); + goto failed; + } + + return; + +failed: + setup_remove(setup); +} + +static void sep_open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_device *dev; + + DBG(""); + + if (err) + goto failed; + + dev = find_device_by_session(session); + if (!dev) { + error("Unable to find device for session"); + goto failed; + } + + a2dp_device_connect(dev, transport_connect_cb); + + return; + +failed: + setup_remove_by_id(endpoint->id); +} + +static void sep_start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + if (err) { + setup_remove_by_id(endpoint->id); + return; + } + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for %u endpoint", + endpoint->id); + return; + } + + bt_audio_notify_state(setup, HAL_AUDIO_STARTED); +} + +static void sep_suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + if (err) { + setup_remove_by_id(endpoint->id); + return; + } + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for %u endpoint", + endpoint->id); + return; + } + + bt_audio_notify_state(setup, HAL_AUDIO_STOPPED); +} + +static void sep_close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + struct a2dp_setup *setup; + + DBG(""); + + if (err) + return; + + setup = find_setup(endpoint->id); + if (!setup) { + error("Unable to find stream setup for %u endpoint", + endpoint->id); + return; + } + + bt_audio_notify_state(setup, HAL_AUDIO_STOPPED); + + setup_remove(setup); +} + +static void sep_abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_endpoint *endpoint = user_data; + + DBG(""); + + if (err) + return; + + setup_remove_by_id(endpoint->id); +} + +static struct avdtp_sep_cfm sep_cfm = { + .set_configuration = sep_setconf_cfm, + .open = sep_open_cfm, + .start = sep_start_cfm, + .suspend = sep_suspend_cfm, + .close = sep_close_cfm, + .abort = sep_abort_cfm, +}; + +static uint8_t register_endpoint(const uint8_t *uuid, uint8_t codec, + GSList *presets) +{ + struct a2dp_endpoint *endpoint; + + /* FIXME: Add proper check for uuid */ + + endpoint = g_new0(struct a2dp_endpoint, 1); + endpoint->id = g_slist_length(endpoints) + 1; + endpoint->codec = codec; + endpoint->sep = avdtp_register_sep(lseps, AVDTP_SEP_TYPE_SOURCE, + AVDTP_MEDIA_TYPE_AUDIO, + codec, FALSE, &sep_ind, + &sep_cfm, endpoint); + endpoint->caps = presets->data; + endpoint->presets = g_slist_copy(g_slist_nth(presets, 1)); + + if (endpoint->codec == A2DP_CODEC_VENDOR) { + a2dp_vendor_codec_t *vndcodec = (void *) endpoint->caps->data; + + avdtp_sep_set_vendor_codec(endpoint->sep, + btohl(vndcodec->vendor_id), + btohs(vndcodec->codec_id)); + } + + endpoints = g_slist_append(endpoints, endpoint); + + return endpoint->id; +} + +static GSList *parse_presets(const struct audio_preset *p, uint8_t count, + uint16_t len) +{ + GSList *l = NULL; + uint8_t i; + + for (i = 0; count > i; i++) { + const uint8_t *ptr = (const uint8_t *) p; + struct a2dp_preset *preset; + + if (len < sizeof(struct audio_preset)) { + DBG("Invalid preset index %u", i); + g_slist_free_full(l, preset_free); + return NULL; + } + + len -= sizeof(struct audio_preset); + if (len == 0 || len < p->len) { + DBG("Invalid preset size of %u for index %u", len, i); + g_slist_free_full(l, preset_free); + return NULL; + } + + preset = g_new0(struct a2dp_preset, 1); + preset->len = p->len; + preset->data = g_memdup(p->data, preset->len); + l = g_slist_append(l, preset); + + len -= preset->len; + ptr += sizeof(*p) + preset->len; + p = (const struct audio_preset *) ptr; + } + + return l; +} + +static void bt_audio_open(const void *buf, uint16_t len) +{ + const struct audio_cmd_open *cmd = buf; + struct audio_rsp_open rsp; + GSList *presets; + + DBG(""); + + audio_retrying = false; + + if (cmd->presets == 0) { + error("No audio presets found"); + goto failed; + } + + presets = parse_presets(cmd->preset, cmd->presets, len - sizeof(*cmd)); + if (!presets) { + error("No audio presets found"); + goto failed; + } + + rsp.id = register_endpoint(cmd->uuid, cmd->codec, presets); + if (rsp.id == 0) { + g_slist_free_full(presets, preset_free); + error("Unable to register endpoint"); + goto failed; + } + + g_slist_free(presets); + + ipc_send_rsp_full(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN, + sizeof(rsp), &rsp, -1); + + return; + +failed: + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN, + AUDIO_STATUS_FAILED); +} + +static struct a2dp_endpoint *find_endpoint(uint8_t id) +{ + GSList *l; + + for (l = endpoints; l; l = g_slist_next(l)) { + struct a2dp_endpoint *endpoint = l->data; + + if (endpoint->id == id) + return endpoint; + } + + return NULL; +} + +static void bt_audio_close(const void *buf, uint16_t len) +{ + const struct audio_cmd_close *cmd = buf; + struct a2dp_endpoint *endpoint; + + DBG(""); + + endpoint = find_endpoint(cmd->id); + if (!endpoint) { + error("Unable to find endpoint %u", cmd->id); + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE, + AUDIO_STATUS_FAILED); + return; + } + + endpoints = g_slist_remove(endpoints, endpoint); + unregister_endpoint(endpoint); + + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE, + AUDIO_STATUS_SUCCESS); +} + +static void bt_stream_open(const void *buf, uint16_t len) +{ + const struct audio_cmd_open_stream *cmd = buf; + struct audio_rsp_open_stream *rsp; + struct a2dp_setup *setup; + int fd; + uint16_t omtu; + + DBG(""); + + if (cmd->id) + setup = find_setup(cmd->id); + else + setup = setups ? setups->data : NULL; + if (!setup) { + error("Unable to find stream for endpoint %u", cmd->id); + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, + AUDIO_STATUS_FAILED); + return; + } + + if (!avdtp_stream_get_transport(setup->stream, &fd, NULL, &omtu, + NULL)) { + error("avdtp_stream_get_transport: failed"); + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, + AUDIO_STATUS_FAILED); + return; + } + + len = sizeof(struct audio_rsp_open_stream) + + sizeof(struct audio_preset) + setup->preset->len; + rsp = g_malloc0(len); + rsp->id = setup->endpoint->id; + rsp->mtu = omtu; + rsp->preset->len = setup->preset->len; + memcpy(rsp->preset->data, setup->preset->data, setup->preset->len); + + ipc_send_rsp_full(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, + len, rsp, fd); + + g_free(rsp); +} + +static void bt_stream_close(const void *buf, uint16_t len) +{ + const struct audio_cmd_close_stream *cmd = buf; + struct a2dp_setup *setup; + int err; + + DBG(""); + + setup = find_setup(cmd->id); + if (!setup) { + error("Unable to find stream for endpoint %u", cmd->id); + goto failed; + } + + err = avdtp_close(setup->dev->session, setup->stream, FALSE); + if (err < 0) { + error("avdtp_close: %s", strerror(-err)); + goto failed; + } + + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE_STREAM, + AUDIO_STATUS_SUCCESS); + + return; + +failed: + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_CLOSE_STREAM, + AUDIO_STATUS_FAILED); +} + +static void bt_stream_resume(const void *buf, uint16_t len) +{ + const struct audio_cmd_resume_stream *cmd = buf; + struct a2dp_setup *setup; + int err; + + DBG(""); + + setup = find_setup(cmd->id); + if (!setup) { + error("Unable to find stream for endpoint %u", cmd->id); + goto failed; + } + + if (setup->state != HAL_AUDIO_STARTED) { + err = avdtp_start(setup->dev->session, setup->stream); + if (err < 0) { + error("avdtp_start: %s", strerror(-err)); + goto failed; + } + } + + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_RESUME_STREAM, + AUDIO_STATUS_SUCCESS); + + return; + +failed: + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_RESUME_STREAM, + AUDIO_STATUS_FAILED); +} + +static void bt_stream_suspend(const void *buf, uint16_t len) +{ + const struct audio_cmd_suspend_stream *cmd = buf; + struct a2dp_setup *setup; + int err; + + DBG(""); + + setup = find_setup(cmd->id); + if (!setup) { + error("Unable to find stream for endpoint %u", cmd->id); + goto failed; + } + + err = avdtp_suspend(setup->dev->session, setup->stream); + if (err < 0) { + error("avdtp_suspend: %s", strerror(-err)); + goto failed; + } + + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_SUSPEND_STREAM, + AUDIO_STATUS_SUCCESS); + + return; + +failed: + ipc_send_rsp(audio_ipc, AUDIO_SERVICE_ID, AUDIO_OP_SUSPEND_STREAM, + AUDIO_STATUS_FAILED); +} + +static const struct ipc_handler audio_handlers[] = { + /* AUDIO_OP_OPEN */ + { bt_audio_open, true, sizeof(struct audio_cmd_open) }, + /* AUDIO_OP_CLOSE */ + { bt_audio_close, false, sizeof(struct audio_cmd_close) }, + /* AUDIO_OP_OPEN_STREAM */ + { bt_stream_open, false, sizeof(struct audio_cmd_open_stream) }, + /* AUDIO_OP_CLOSE_STREAM */ + { bt_stream_close, false, sizeof(struct audio_cmd_close_stream) }, + /* AUDIO_OP_RESUME_STREAM */ + { bt_stream_resume, false, sizeof(struct audio_cmd_resume_stream) }, + /* AUDIO_OP_SUSPEND_STREAM */ + { bt_stream_suspend, false, sizeof(struct audio_cmd_suspend_stream) }, +}; + +static void bt_audio_unregister(void) +{ + DBG(""); + + if (audio_retry_id > 0) + g_source_remove(audio_retry_id); + + g_slist_free_full(endpoints, unregister_endpoint); + endpoints = NULL; + + g_slist_free_full(setups, setup_free); + setups = NULL; + + ipc_cleanup(audio_ipc); + audio_ipc = NULL; + + queue_destroy(lseps, NULL); +} + +static bool bt_audio_register(ipc_disconnect_cb disconnect) +{ + DBG(""); + + audio_ipc = ipc_init(BLUEZ_AUDIO_SK_PATH, sizeof(BLUEZ_AUDIO_SK_PATH), + AUDIO_SERVICE_ID_MAX, false, disconnect, NULL); + if (!audio_ipc) + return false; + + ipc_register(audio_ipc, AUDIO_SERVICE_ID, audio_handlers, + G_N_ELEMENTS(audio_handlers)); + + return true; +} + +static gboolean audio_retry_register(void *data) +{ + ipc_disconnect_cb cb = data; + + audio_retry_id = 0; + audio_retrying = true; + + bt_audio_register(cb); + + return FALSE; +} + +static void audio_disconnected(void *data) +{ + GSList *l; + bool restart; + + DBG(""); + + if (audio_retrying) + goto retry; + + restart = endpoints != NULL ? true : false; + + bt_audio_unregister(); + + for (l = devices; l; l = g_slist_next(l)) { + struct a2dp_device *dev = l->data; + + avdtp_shutdown(dev->session); + } + + if (!restart) + return; + +retry: + audio_retry_id = g_timeout_add_seconds(AUDIO_RETRY_TIMEOUT, + audio_retry_register, + audio_disconnected); +} + +bool bt_a2dp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + GError *err = NULL; + sdp_record_t *rec; + + DBG(""); + + bacpy(&adapter_addr, addr); + + lseps = queue_new(); + + server = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, true, + BT_IO_OPT_INVALID); + if (!server) { + error("Failed to listen on AVDTP channel: %s", err->message); + g_error_free(err); + return false; + } + + rec = a2dp_record(); + if (!rec) { + error("Failed to allocate A2DP record"); + goto fail; + } + + if (bt_adapter_add_record(rec, SVC_HINT_CAPTURING) < 0) { + error("Failed to register A2DP record"); + sdp_record_free(rec); + goto fail; + } + record_id = rec->handle; + + hal_ipc = ipc; + + ipc_register(hal_ipc, HAL_SERVICE_ID_A2DP, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + if (bt_audio_register(audio_disconnected)) + return true; + +fail: + g_io_channel_shutdown(server, TRUE, NULL); + g_io_channel_unref(server); + server = NULL; + return false; +} + +void bt_a2dp_unregister(void) +{ + DBG(""); + + g_slist_free_full(setups, setup_free); + setups = NULL; + + g_slist_free_full(endpoints, unregister_endpoint); + endpoints = NULL; + + g_slist_free_full(devices, a2dp_device_free); + devices = NULL; + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_A2DP); + hal_ipc = NULL; + + bt_adapter_remove_record(record_id); + record_id = 0; + + if (server) { + g_io_channel_shutdown(server, TRUE, NULL); + g_io_channel_unref(server); + server = NULL; + } + + if (audio_ipc) { + ipc_unregister(audio_ipc, AUDIO_SERVICE_ID); + ipc_cleanup(audio_ipc); + audio_ipc = NULL; + } +}
diff --git a/android/a2dp.h b/android/a2dp.h new file mode 100644 index 0000000..8a70407 --- /dev/null +++ b/android/a2dp.h
@@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_a2dp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_a2dp_unregister(void);
diff --git a/android/audio-ipc-api.txt b/android/audio-ipc-api.txt new file mode 100644 index 0000000..f4a497d --- /dev/null +++ b/android/audio-ipc-api.txt
@@ -0,0 +1,87 @@ +Bluetooth Audio Plugin +====================== + +The audio plugin happen to be in a different socket but all the rules for +HAL socket apply here as well, the abstract socket name is +"\0bluez_audio_socket" (tentative): + + .---Audio---. .--Android--. + | Plugin | | Daemon | + | | Command | | + | | --------------------------> | | + | | | | + | | <-------------------------- | | + | | Response | | + | | | | + | | | | + | | | | + '-----------' '-----------' + + + Audio HAL Daemon + ---------------------------------------------------- + + call dev->open() --> command 0x01 + return dev->open() <-- response 0x01 + + call dev->open_output_stream() --> command 0x03 + return dev->open_output_stream() <-- response 0x03 + + call stream->write() --> command 0x05 + return stream->write() <-- response 0x05 + + call stream->common.standby() --> command 0x06 + return stream->common.standby() <-- response 0x06 + + call dev->close_output_stream() --> command 0x04 + return dev->close_output_stream() <-- response 0x04 + + call dev->close() --> command 0x02 + return dev->close() <-- response 0x02 + +Audio Service (ID 0) +==================== + + Opcode 0x00 - Error response + + Response parameters: Status (1 octet) + + Opcode 0x01 - Open Audio Endpoint commmand + + Command parameters: Service UUID (16 octets) + Codec ID (1 octet) + Number of codec presets (1 octet) + Codec capabilities length (1 octet) + Codec capabilities (variable) + Codec preset # length (1 octet) + Codec preset # configuration (variable) + ... + Response parameters: Endpoint ID (1 octet) + + Opcode 0x02 - Close Audio Endpoint command + + Command parameters: Endpoint ID (1 octet) + Response parameters: <none> + + Opcode 0x03 - Open Stream command + + Command parameters: Endpoint ID (1 octet) + Response parameters: Outgoing MTU (2 octets) + Codec configuration length (1 octet) + Codec configuration (1 octet) + File descriptor (inline) + + Opcode 0x04 - Close Stream command + + Command parameters: Endpoint ID (1 octet) + Response parameters: <none> + + Opcode 0x05 - Resume Stream command + + Command parameters: Endpoint ID (1 octet) + Response parameters: <none> + + Opcode 0x06 - Suspend Stream command + + Command parameters: Endpoint ID (1 octet) + Response parameters: <none>
diff --git a/android/audio-msg.h b/android/audio-msg.h new file mode 100644 index 0000000..7b9553b --- /dev/null +++ b/android/audio-msg.h
@@ -0,0 +1,82 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 BLUEZ_AUDIO_MTU 1024 + +static const char BLUEZ_AUDIO_SK_PATH[] = "\0bluez_audio_socket"; + +#define AUDIO_SERVICE_ID 0 +#define AUDIO_SERVICE_ID_MAX AUDIO_SERVICE_ID + +#define AUDIO_STATUS_SUCCESS IPC_STATUS_SUCCESS +#define AUDIO_STATUS_FAILED 0x01 + +#define AUDIO_OP_STATUS IPC_OP_STATUS + +#define AUDIO_OP_OPEN 0x01 +struct audio_preset { + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +struct audio_cmd_open { + uint8_t uuid[16]; + uint8_t codec; + uint8_t presets; + struct audio_preset preset[0]; +} __attribute__((packed)); + +struct audio_rsp_open { + uint8_t id; +} __attribute__((packed)); + +#define AUDIO_OP_CLOSE 0x02 +struct audio_cmd_close { + uint8_t id; +} __attribute__((packed)); + +#define AUDIO_OP_OPEN_STREAM 0x03 +struct audio_cmd_open_stream { + uint8_t id; +} __attribute__((packed)); + +struct audio_rsp_open_stream { + uint16_t id; + uint16_t mtu; + struct audio_preset preset[0]; +} __attribute__((packed)); + +#define AUDIO_OP_CLOSE_STREAM 0x04 +struct audio_cmd_close_stream { + uint8_t id; +} __attribute__((packed)); + +#define AUDIO_OP_RESUME_STREAM 0x05 +struct audio_cmd_resume_stream { + uint8_t id; +} __attribute__((packed)); + +#define AUDIO_OP_SUSPEND_STREAM 0x06 +struct audio_cmd_suspend_stream { + uint8_t id; +} __attribute__((packed));
diff --git a/android/audio_utils/resampler.c b/android/audio_utils/resampler.c new file mode 100644 index 0000000..ce30375 --- /dev/null +++ b/android/audio_utils/resampler.c
@@ -0,0 +1,270 @@ +/* +** Copyright 2011, 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. +*/ + +//#define LOG_NDEBUG 0 + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <system/audio.h> +#include <audio_utils/resampler.h> +#include <speex/speex_resampler.h> + +#include "hal-log.h" + +struct resampler { + struct resampler_itfe itfe; + SpeexResamplerState *speex_resampler; // handle on speex resampler + struct resampler_buffer_provider *provider; // buffer provider installed by client + uint32_t in_sample_rate; // input sampling rate in Hz + uint32_t out_sample_rate; // output sampling rate in Hz + uint32_t channel_count; // number of channels (interleaved) + int16_t *in_buf; // input buffer + size_t in_buf_size; // input buffer size + size_t frames_in; // number of frames in input buffer + size_t frames_rq; // cached number of output frames + size_t frames_needed; // minimum number of input frames to produce + // frames_rq output frames + int32_t speex_delay_ns; // delay introduced by speex resampler in ns +}; + + +//------------------------------------------------------------------------------ +// speex based resampler +//------------------------------------------------------------------------------ + +static void resampler_reset(struct resampler_itfe *resampler) +{ + struct resampler *rsmp = (struct resampler *)resampler; + + rsmp->frames_in = 0; + rsmp->frames_rq = 0; + + if (rsmp != NULL && rsmp->speex_resampler != NULL) { + speex_resampler_reset_mem(rsmp->speex_resampler); + } +} + +static int32_t resampler_delay_ns(struct resampler_itfe *resampler) +{ + struct resampler *rsmp = (struct resampler *)resampler; + + int32_t delay = (int32_t)((1000000000 * (int64_t)rsmp->frames_in) / rsmp->in_sample_rate); + delay += rsmp->speex_delay_ns; + + return delay; +} + +// outputs a number of frames less or equal to *outFrameCount and updates *outFrameCount +// with the actual number of frames produced. +static int resampler_resample_from_provider(struct resampler_itfe *resampler, + int16_t *out, + size_t *outFrameCount) +{ + struct resampler *rsmp = (struct resampler *)resampler; + size_t framesRq; + size_t framesWr; + size_t inFrames; + + if (rsmp == NULL || out == NULL || outFrameCount == NULL) { + return -EINVAL; + } + if (rsmp->provider == NULL) { + *outFrameCount = 0; + return -ENOSYS; + } + + framesRq = *outFrameCount; + // update and cache the number of frames needed at the input sampling rate to produce + // the number of frames requested at the output sampling rate + if (framesRq != rsmp->frames_rq) { + rsmp->frames_needed = (framesRq * rsmp->in_sample_rate) / rsmp->out_sample_rate + 1; + rsmp->frames_rq = framesRq; + } + + framesWr = 0; + inFrames = 0; + while (framesWr < framesRq) { + size_t outFrames; + if (rsmp->frames_in < rsmp->frames_needed) { + struct resampler_buffer buf; + // make sure that the number of frames present in rsmp->in_buf (rsmp->frames_in) is at + // least the number of frames needed to produce the number of frames requested at + // the output sampling rate + if (rsmp->in_buf_size < rsmp->frames_needed) { + rsmp->in_buf_size = rsmp->frames_needed; + rsmp->in_buf = (int16_t *)realloc(rsmp->in_buf, + rsmp->in_buf_size * rsmp->channel_count * sizeof(int16_t)); + } + buf.frame_count = rsmp->frames_needed - rsmp->frames_in; + rsmp->provider->get_next_buffer(rsmp->provider, &buf); + if (buf.raw == NULL) { + break; + } + memcpy(rsmp->in_buf + rsmp->frames_in * rsmp->channel_count, + buf.raw, + buf.frame_count * rsmp->channel_count * sizeof(int16_t)); + rsmp->frames_in += buf.frame_count; + rsmp->provider->release_buffer(rsmp->provider, &buf); + } + + outFrames = framesRq - framesWr; + inFrames = rsmp->frames_in; + if (rsmp->channel_count == 1) { + speex_resampler_process_int(rsmp->speex_resampler, + 0, + rsmp->in_buf, + (void *) &inFrames, + out + framesWr, + (void *) &outFrames); + } else { + speex_resampler_process_interleaved_int(rsmp->speex_resampler, + rsmp->in_buf, + (void *) &inFrames, + out + framesWr * rsmp->channel_count, + (void *) &outFrames); + } + framesWr += outFrames; + rsmp->frames_in -= inFrames; + + if ((framesWr != framesRq) && (rsmp->frames_in != 0)) + warn("ReSampler::resample() remaining %zd frames in and %zd out", + rsmp->frames_in, (framesRq - framesWr)); + } + if (rsmp->frames_in) { + memmove(rsmp->in_buf, + rsmp->in_buf + inFrames * rsmp->channel_count, + rsmp->frames_in * rsmp->channel_count * sizeof(int16_t)); + } + *outFrameCount = framesWr; + + return 0; +} + +static int resampler_resample_from_input(struct resampler_itfe *resampler, + int16_t *in, + size_t *inFrameCount, + int16_t *out, + size_t *outFrameCount) +{ + struct resampler *rsmp = (struct resampler *)resampler; + + if (rsmp == NULL || in == NULL || inFrameCount == NULL || + out == NULL || outFrameCount == NULL) { + return -EINVAL; + } + if (rsmp->provider != NULL) { + *outFrameCount = 0; + return -ENOSYS; + } + + if (rsmp->channel_count == 1) { + speex_resampler_process_int(rsmp->speex_resampler, + 0, + in, + (void *) inFrameCount, + out, + (void *) outFrameCount); + } else { + speex_resampler_process_interleaved_int(rsmp->speex_resampler, + in, + (void *) inFrameCount, + out, + (void *) outFrameCount); + } + + DBG("resampler_resample_from_input() DONE in %zd out %zd", *inFrameCount, *outFrameCount); + + return 0; +} + +int create_resampler(uint32_t inSampleRate, + uint32_t outSampleRate, + uint32_t channelCount, + uint32_t quality, + struct resampler_buffer_provider* provider, + struct resampler_itfe **resampler) +{ + int error; + struct resampler *rsmp; + int frames; + + DBG("create_resampler() In SR %d Out SR %d channels %d", + inSampleRate, outSampleRate, channelCount); + + if (resampler == NULL) { + return -EINVAL; + } + + *resampler = NULL; + + if (quality <= RESAMPLER_QUALITY_MIN || quality >= RESAMPLER_QUALITY_MAX) { + return -EINVAL; + } + + rsmp = (struct resampler *)calloc(1, sizeof(struct resampler)); + + rsmp->speex_resampler = speex_resampler_init(channelCount, + inSampleRate, + outSampleRate, + quality, + &error); + if (rsmp->speex_resampler == NULL) { + error("ReSampler: Cannot create speex resampler: %s", speex_resampler_strerror(error)); + free(rsmp); + return -ENODEV; + } + + rsmp->itfe.reset = resampler_reset; + rsmp->itfe.resample_from_provider = resampler_resample_from_provider; + rsmp->itfe.resample_from_input = resampler_resample_from_input; + rsmp->itfe.delay_ns = resampler_delay_ns; + + rsmp->provider = provider; + rsmp->in_sample_rate = inSampleRate; + rsmp->out_sample_rate = outSampleRate; + rsmp->channel_count = channelCount; + rsmp->in_buf = NULL; + rsmp->in_buf_size = 0; + + resampler_reset(&rsmp->itfe); + + frames = speex_resampler_get_input_latency(rsmp->speex_resampler); + rsmp->speex_delay_ns = (int32_t)((1000000000 * (int64_t)frames) / rsmp->in_sample_rate); + frames = speex_resampler_get_output_latency(rsmp->speex_resampler); + rsmp->speex_delay_ns += (int32_t)((1000000000 * (int64_t)frames) / rsmp->out_sample_rate); + + *resampler = &rsmp->itfe; + DBG("create_resampler() DONE rsmp %p &rsmp->itfe %p speex %p", + rsmp, &rsmp->itfe, rsmp->speex_resampler); + return 0; +} + +void release_resampler(struct resampler_itfe *resampler) +{ + struct resampler *rsmp = (struct resampler *)resampler; + + if (rsmp == NULL) { + return; + } + + free(rsmp->in_buf); + + if (rsmp->speex_resampler != NULL) { + speex_resampler_destroy(rsmp->speex_resampler); + } + free(rsmp); +}
diff --git a/android/audio_utils/resampler.h b/android/audio_utils/resampler.h new file mode 100644 index 0000000..0c7046f --- /dev/null +++ b/android/audio_utils/resampler.h
@@ -0,0 +1,109 @@ +/* +** Copyright 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. +*/ + +#ifndef ANDROID_RESAMPLER_H +#define ANDROID_RESAMPLER_H + +#include <stdint.h> +#include <sys/time.h> + +__BEGIN_DECLS + + +#define RESAMPLER_QUALITY_MAX 10 +#define RESAMPLER_QUALITY_MIN 0 +#define RESAMPLER_QUALITY_DEFAULT 4 +#define RESAMPLER_QUALITY_VOIP 3 +#define RESAMPLER_QUALITY_DESKTOP 5 + +struct resampler_buffer { + union { + void* raw; + short* i16; + int8_t* i8; + }; + size_t frame_count; +}; + +/* call back interface used by the resampler to get new data */ +struct resampler_buffer_provider +{ + /** + * get a new buffer of data: + * as input: buffer->frame_count is the number of frames requested + * as output: buffer->frame_count is the number of frames returned + * buffer->raw points to data returned + */ + int (*get_next_buffer)(struct resampler_buffer_provider *provider, + struct resampler_buffer *buffer); + /** + * release a consumed buffer of data: + * as input: buffer->frame_count is the number of frames released + * buffer->raw points to data released + */ + void (*release_buffer)(struct resampler_buffer_provider *provider, + struct resampler_buffer *buffer); +}; + +/* resampler interface */ +struct resampler_itfe { + /** + * reset resampler state + */ + void (*reset)(struct resampler_itfe *resampler); + /** + * resample input from buffer provider and output at most *outFrameCount to out buffer. + * *outFrameCount is updated with the actual number of frames produced. + */ + int (*resample_from_provider)(struct resampler_itfe *resampler, + int16_t *out, + size_t *outFrameCount); + /** + * resample at most *inFrameCount frames from in buffer and output at most + * *outFrameCount to out buffer. *inFrameCount and *outFrameCount are updated respectively + * with the number of frames remaining in input and written to output. + */ + int (*resample_from_input)(struct resampler_itfe *resampler, + int16_t *in, + size_t *inFrameCount, + int16_t *out, + size_t *outFrameCount); + /** + * return the latency introduced by the resampler in ns. + */ + int32_t (*delay_ns)(struct resampler_itfe *resampler); +}; + +/** + * create a resampler according to input parameters passed. + * If resampler_buffer_provider is not NULL only resample_from_provider() can be called. + * If resampler_buffer_provider is NULL only resample_from_input() can be called. + */ +int create_resampler(uint32_t inSampleRate, + uint32_t outSampleRate, + uint32_t channelCount, + uint32_t quality, + struct resampler_buffer_provider *provider, + struct resampler_itfe **); + +/** + * release resampler resources. + */ +void release_resampler(struct resampler_itfe *); + +__END_DECLS + +#endif // ANDROID_RESAMPLER_H
diff --git a/android/avctp.c b/android/avctp.c new file mode 100644 index 0000000..6aa64cf --- /dev/null +++ b/android/avctp.c
@@ -0,0 +1,1653 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2011 Texas Instruments, Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <netinet/in.h> + +#include <glib.h> + +#include "lib/sdp.h" + +#include "src/log.h" +#include "src/uinput.h" + +#include "avctp.h" + +/* + * AV/C Panel 1.23, page 76: + * command with the pressed value is valid for two seconds + */ +#define AVC_PRESS_TIMEOUT 2 + +#define QUIRK_NO_RELEASE 1 << 0 + +/* Message types */ +#define AVCTP_COMMAND 0 +#define AVCTP_RESPONSE 1 + +/* Packet types */ +#define AVCTP_PACKET_SINGLE 0 +#define AVCTP_PACKET_START 1 +#define AVCTP_PACKET_CONTINUE 2 +#define AVCTP_PACKET_END 3 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avctp_header { + uint8_t ipid:1; + uint8_t cr:1; + uint8_t packet_type:2; + uint8_t transaction:4; + uint16_t pid; +} __attribute__ ((packed)); + +struct avc_header { + uint8_t code:4; + uint8_t _hdr0:4; + uint8_t subunit_id:3; + uint8_t subunit_type:5; + uint8_t opcode; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avctp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t cr:1; + uint8_t ipid:1; + uint16_t pid; +} __attribute__ ((packed)); + +struct avc_header { + uint8_t _hdr0:4; + uint8_t code:4; + uint8_t subunit_type:5; + uint8_t subunit_id:3; + uint8_t opcode; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct avctp_control_req { + struct avctp_pending_req *p; + uint8_t code; + uint8_t subunit; + uint8_t op; + struct iovec *iov; + int iov_cnt; + avctp_rsp_cb func; + void *user_data; +}; + +struct avctp_browsing_req { + struct avctp_pending_req *p; + struct iovec *iov; + int iov_cnt; + avctp_browsing_rsp_cb func; + void *user_data; +}; + +typedef int (*avctp_process_cb) (void *data); + +struct avctp_pending_req { + struct avctp_channel *chan; + uint8_t transaction; + guint timeout; + int err; + avctp_process_cb process; + void *data; + avctp_destroy_cb_t destroy; +}; + +struct avctp_channel { + struct avctp *session; + GIOChannel *io; + uint8_t transaction; + guint watch; + uint16_t imtu; + uint16_t omtu; + uint8_t *buffer; + GSList *handlers; + struct avctp_pending_req *p; + GQueue *queue; + GSList *processed; + guint process_id; + avctp_destroy_cb_t destroy; +}; + +struct key_pressed { + uint8_t op; + uint8_t *params; + size_t params_len; + guint timer; +}; + +struct avctp { + unsigned int ref; + int uinput; + + unsigned int passthrough_id; + unsigned int unit_id; + unsigned int subunit_id; + + struct avctp_channel *control; + struct avctp_channel *browsing; + + struct avctp_passthrough_handler *handler; + + uint8_t key_quirks[256]; + struct key_pressed key; + uint16_t version; + + avctp_destroy_cb_t destroy; + void *data; +}; + +struct avctp_passthrough_handler { + avctp_passthrough_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_pdu_handler { + uint8_t opcode; + avctp_control_pdu_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_browsing_pdu_handler { + avctp_browsing_pdu_cb cb; + void *user_data; + unsigned int id; + avctp_destroy_cb_t destroy; +}; + +static struct { + const char *name; + uint8_t avc; + uint16_t uinput; +} key_map[] = { + { "SELECT", AVC_SELECT, KEY_SELECT }, + { "UP", AVC_UP, KEY_UP }, + { "DOWN", AVC_DOWN, KEY_DOWN }, + { "LEFT", AVC_LEFT, KEY_LEFT }, + { "RIGHT", AVC_RIGHT, KEY_RIGHT }, + { "ROOT MENU", AVC_ROOT_MENU, KEY_MENU }, + { "CONTENTS MENU", AVC_CONTENTS_MENU, KEY_PROGRAM }, + { "FAVORITE MENU", AVC_FAVORITE_MENU, KEY_FAVORITES }, + { "EXIT", AVC_EXIT, KEY_EXIT }, + { "ON DEMAND MENU", AVC_ON_DEMAND_MENU, KEY_MENU }, + { "APPS MENU", AVC_APPS_MENU, KEY_MENU }, + { "0", AVC_0, KEY_0 }, + { "1", AVC_1, KEY_1 }, + { "2", AVC_2, KEY_2 }, + { "3", AVC_3, KEY_3 }, + { "4", AVC_4, KEY_4 }, + { "5", AVC_5, KEY_5 }, + { "6", AVC_6, KEY_6 }, + { "7", AVC_7, KEY_7 }, + { "8", AVC_8, KEY_8 }, + { "9", AVC_9, KEY_9 }, + { "DOT", AVC_DOT, KEY_DOT }, + { "ENTER", AVC_ENTER, KEY_ENTER }, + { "CHANNEL UP", AVC_CHANNEL_UP, KEY_CHANNELUP }, + { "CHANNEL DOWN", AVC_CHANNEL_DOWN, KEY_CHANNELDOWN }, + { "CHANNEL PREVIOUS", AVC_CHANNEL_PREVIOUS, KEY_LAST }, + { "INPUT SELECT", AVC_INPUT_SELECT, KEY_CONFIG }, + { "INFO", AVC_INFO, KEY_INFO }, + { "HELP", AVC_HELP, KEY_HELP }, + { "POWER", AVC_POWER, KEY_POWER2 }, + { "VOLUME UP", AVC_VOLUME_UP, KEY_VOLUMEUP }, + { "VOLUME DOWN", AVC_VOLUME_DOWN, KEY_VOLUMEDOWN }, + { "MUTE", AVC_MUTE, KEY_MUTE }, + { "PLAY", AVC_PLAY, KEY_PLAYCD }, + { "STOP", AVC_STOP, KEY_STOPCD }, + { "PAUSE", AVC_PAUSE, KEY_PAUSECD }, + { "FORWARD", AVC_FORWARD, KEY_NEXTSONG }, + { "BACKWARD", AVC_BACKWARD, KEY_PREVIOUSSONG }, + { "RECORD", AVC_RECORD, KEY_RECORD }, + { "REWIND", AVC_REWIND, KEY_REWIND }, + { "FAST FORWARD", AVC_FAST_FORWARD, KEY_FASTFORWARD }, + { "LIST", AVC_LIST, KEY_LIST }, + { "F1", AVC_F1, KEY_F1 }, + { "F2", AVC_F2, KEY_F2 }, + { "F3", AVC_F3, KEY_F3 }, + { "F4", AVC_F4, KEY_F4 }, + { "F5", AVC_F5, KEY_F5 }, + { "F6", AVC_F6, KEY_F6 }, + { "F7", AVC_F7, KEY_F7 }, + { "F8", AVC_F8, KEY_F8 }, + { "F9", AVC_F9, KEY_F9 }, + { "RED", AVC_RED, KEY_RED }, + { "GREEN", AVC_GREEN, KEY_GREEN }, + { "BLUE", AVC_BLUE, KEY_BLUE }, + { "YELLOW", AVC_YELLOW, KEY_YELLOW }, + { NULL } +}; + +static gboolean process_queue(gpointer user_data); +static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t *operands, + size_t operand_count, void *user_data); + +static int 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; + + do { + err = write(fd, &event, sizeof(event)); + } while (err < 0 && errno == EINTR); + + if (err < 0) { + err = -errno; + error("send_event: %s (%d)", strerror(-err), -err); + } + + return err; +} + +static void send_key(int fd, uint16_t key, int pressed) +{ + send_event(fd, EV_KEY, key, pressed); + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static gboolean auto_release(gpointer user_data) +{ + struct avctp *session = user_data; + + session->key.timer = 0; + + DBG("AV/C: key press timeout"); + + send_key(session->uinput, session->key.op, 0); + + return FALSE; +} + +static ssize_t handle_panel_passthrough(struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avctp_passthrough_handler *handler = session->handler; + const char *status; + int pressed, i; + + if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) { + *code = AVC_CTYPE_REJECTED; + return operand_count; + } + + if (operand_count == 0) + goto done; + + if (operands[0] & 0x80) { + status = "released"; + pressed = 0; + } else { + status = "pressed"; + pressed = 1; + } + + if (session->key.timer == 0 && handler != NULL) { + if (handler->cb(session, operands[0] & 0x7F, + pressed, handler->user_data)) + goto done; + } + + if (session->uinput < 0) { + DBG("AV/C: uinput not initialized"); + *code = AVC_CTYPE_NOT_IMPLEMENTED; + return 0; + } + + for (i = 0; key_map[i].name != NULL; i++) { + uint8_t key_quirks; + + if ((operands[0] & 0x7F) != key_map[i].avc) + continue; + + DBG("AV/C: %s %s", key_map[i].name, status); + + key_quirks = session->key_quirks[key_map[i].avc]; + + if (key_quirks & QUIRK_NO_RELEASE) { + if (!pressed) { + DBG("AV/C: Ignoring release"); + break; + } + + DBG("AV/C: treating key press as press + release"); + send_key(session->uinput, key_map[i].uinput, 1); + send_key(session->uinput, key_map[i].uinput, 0); + break; + } + + if (pressed) { + if (session->key.timer > 0) { + g_source_remove(session->key.timer); + send_key(session->uinput, session->key.op, 0); + } + + session->key.op = key_map[i].uinput; + session->key.timer = g_timeout_add_seconds( + AVC_PRESS_TIMEOUT, + auto_release, + session); + } else if (session->key.timer > 0) { + g_source_remove(session->key.timer); + session->key.timer = 0; + } + + send_key(session->uinput, key_map[i].uinput, pressed); + break; + } + + if (key_map[i].name == NULL) { + DBG("AV/C: unknown button 0x%02X %s", + operands[0] & 0x7F, status); + *code = AVC_CTYPE_NOT_IMPLEMENTED; + return operand_count; + } + +done: + *code = AVC_CTYPE_ACCEPTED; + return operand_count; +} + +static ssize_t handle_unit_info(struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + if (*code != AVC_CTYPE_STATUS) { + *code = AVC_CTYPE_REJECTED; + return 0; + } + + *code = AVC_CTYPE_STABLE; + + /* + * The first operand should be 0x07 for the UNITINFO response. + * Neither AVRCP (section 22.1, page 117) nor AVC Digital + * Interface Command Set (section 9.2.1, page 45) specs + * explain this value but both use it + */ + if (operand_count >= 1) + operands[0] = 0x07; + if (operand_count >= 2) + operands[1] = AVC_SUBUNIT_PANEL << 3; + + DBG("reply to AVC_OP_UNITINFO"); + + return operand_count; +} + +static ssize_t handle_subunit_info(struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + if (*code != AVC_CTYPE_STATUS) { + *code = AVC_CTYPE_REJECTED; + return 0; + } + + *code = AVC_CTYPE_STABLE; + + /* + * The first operand should be 0x07 for the UNITINFO response. + * Neither AVRCP (section 22.1, page 117) nor AVC Digital + * Interface Command Set (section 9.2.1, page 45) specs + * explain this value but both use it + */ + if (operand_count >= 2) + operands[1] = AVC_SUBUNIT_PANEL << 3; + + DBG("reply to AVC_OP_SUBUNITINFO"); + + return operand_count; +} + +static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode) +{ + for (; list; list = list->next) { + struct avctp_pdu_handler *handler = list->data; + + if (handler->opcode == opcode) + return handler; + } + + return NULL; +} + +static void pending_destroy(gpointer data, gpointer user_data) +{ + struct avctp_pending_req *req = data; + + if (req->destroy) + req->destroy(req->data); + + if (req->timeout > 0) + g_source_remove(req->timeout); + + g_free(req); +} + +static void avctp_channel_destroy(struct avctp_channel *chan) +{ + g_io_channel_shutdown(chan->io, TRUE, NULL); + g_io_channel_unref(chan->io); + + if (chan->watch) + g_source_remove(chan->watch); + + if (chan->p) + pending_destroy(chan->p, NULL); + + if (chan->process_id > 0) + g_source_remove(chan->process_id); + + if (chan->destroy) + chan->destroy(chan); + + g_free(chan->buffer); + g_queue_foreach(chan->queue, pending_destroy, NULL); + g_queue_free(chan->queue); + g_slist_foreach(chan->processed, pending_destroy, NULL); + g_slist_free(chan->processed); + g_slist_free_full(chan->handlers, g_free); + g_free(chan); +} + +static int avctp_send(struct avctp_channel *control, uint8_t transaction, + uint8_t cr, uint8_t code, + uint8_t subunit, uint8_t opcode, + const struct iovec *iov, int iov_cnt) +{ + struct avctp_header avctp; + struct avc_header avc; + struct msghdr msg; + int sk, err = 0; + struct iovec pdu[iov_cnt + 2]; + int i; + size_t len = sizeof(avctp) + sizeof(avc); + + DBG(""); + + pdu[0].iov_base = &avctp; + pdu[0].iov_len = sizeof(avctp); + pdu[1].iov_base = &avc; + pdu[1].iov_len = sizeof(avc); + + for (i = 0; i < iov_cnt; i++) { + pdu[i + 2].iov_base = iov[i].iov_base; + pdu[i + 2].iov_len = iov[i].iov_len; + len += iov[i].iov_len; + } + + if (control->omtu < len) + return -EOVERFLOW; + + sk = g_io_channel_unix_get_fd(control->io); + + memset(&avctp, 0, sizeof(avctp)); + + avctp.transaction = transaction; + avctp.packet_type = AVCTP_PACKET_SINGLE; + avctp.cr = cr; + avctp.pid = htons(AV_REMOTE_SVCLASS_ID); + + memset(&avc, 0, sizeof(avc)); + + avc.code = code; + avc.subunit_type = subunit; + avc.opcode = opcode; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = pdu; + msg.msg_iovlen = iov_cnt + 2; + + if (sendmsg(sk, &msg, 0) < 0) + err = -errno; + + return err; +} + +static int avctp_browsing_send(struct avctp_channel *browsing, + uint8_t transaction, uint8_t cr, + const struct iovec *iov, int iov_cnt) +{ + struct avctp_header avctp; + struct msghdr msg; + struct iovec pdu[iov_cnt + 1]; + int sk, err = 0; + int i; + size_t len = sizeof(avctp); + + for (i = 0; i < iov_cnt; i++) { + pdu[i + 1].iov_base = iov[i].iov_base; + pdu[i + 1].iov_len = iov[i].iov_len; + len += iov[i].iov_len; + } + + pdu[0].iov_base = &avctp; + pdu[0].iov_len = sizeof(avctp); + + if (browsing->omtu < len) + return -EOVERFLOW; + + sk = g_io_channel_unix_get_fd(browsing->io); + + memset(&avctp, 0, sizeof(avctp)); + + avctp.transaction = transaction; + avctp.packet_type = AVCTP_PACKET_SINGLE; + avctp.cr = cr; + avctp.pid = htons(AV_REMOTE_SVCLASS_ID); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = pdu; + msg.msg_iovlen = iov_cnt + 1; + + if (sendmsg(sk, &msg, 0) < 0) + err = -errno; + + return err; +} + +static void control_req_destroy(void *data) +{ + struct avctp_control_req *req = data; + struct avctp_pending_req *p = req->p; + struct avctp *session = p->chan->session; + int i; + + if (p->err == 0 || req->func == NULL) + goto done; + + req->func(session, AVC_CTYPE_REJECTED, req->subunit, NULL, 0, + req->user_data); + +done: + for (i = 0; i < req->iov_cnt; i++) + g_free(req->iov[i].iov_base); + + g_free(req->iov); + g_free(req); +} + +static void browsing_req_destroy(void *data) +{ + struct avctp_browsing_req *req = data; + struct avctp_pending_req *p = req->p; + struct avctp *session = p->chan->session; + int i; + + if (p->err == 0 || req->func == NULL) + goto done; + + req->func(session, NULL, 0, req->user_data); + +done: + for (i = 0; i < req->iov_cnt; i++) + g_free(req->iov[i].iov_base); + + g_free(req->iov); + g_free(req); +} + +static gboolean req_timeout(gpointer user_data) +{ + struct avctp_channel *chan = user_data; + struct avctp_pending_req *p = chan->p; + + DBG("transaction %u", p->transaction); + + p->timeout = 0; + p->err = -ETIMEDOUT; + + pending_destroy(p, NULL); + chan->p = NULL; + + if (chan->process_id == 0) + chan->process_id = g_idle_add(process_queue, chan); + + return FALSE; +} + +static int process_control(void *data) +{ + struct avctp_control_req *req = data; + struct avctp_pending_req *p = req->p; + + return avctp_send(p->chan, p->transaction, AVCTP_COMMAND, req->code, + req->subunit, req->op, req->iov, req->iov_cnt); +} + +static int process_browsing(void *data) +{ + struct avctp_browsing_req *req = data; + struct avctp_pending_req *p = req->p; + + return avctp_browsing_send(p->chan, p->transaction, AVCTP_COMMAND, + req->iov, req->iov_cnt); +} + +static gboolean process_queue(void *user_data) +{ + struct avctp_channel *chan = user_data; + struct avctp_pending_req *p = chan->p; + + chan->process_id = 0; + + if (p != NULL) + return FALSE; + + while ((p = g_queue_pop_head(chan->queue))) { + + if (p->process(p->data) == 0) + break; + + pending_destroy(p, NULL); + } + + if (p == NULL) + return FALSE; + + chan->p = p; + p->timeout = g_timeout_add_seconds(2, req_timeout, chan); + + return FALSE; + +} + +static struct avctp *avctp_ref(struct avctp *session) +{ + __sync_fetch_and_add(&session->ref, 1); + + DBG("%p: ref=%d", session, session->ref); + + return session; +} + +static void avctp_unref(struct avctp *session) +{ + DBG("%p: ref=%d", session, session->ref); + + if (__sync_sub_and_fetch(&session->ref, 1)) + return; + + if (session->browsing) + avctp_channel_destroy(session->browsing); + + if (session->control) + avctp_channel_destroy(session->control); + + if (session->destroy) + session->destroy(session->data); + + g_free(session->handler); + + if (session->key.timer > 0) + g_source_remove(session->key.timer); + + if (session->uinput >= 0) { + DBG("AVCTP: closing uinput"); + + ioctl(session->uinput, UI_DEV_DESTROY); + close(session->uinput); + session->uinput = -1; + } + + g_free(session); +} + +static void control_response(struct avctp_channel *control, + struct avctp_header *avctp, + struct avc_header *avc, + uint8_t *operands, + size_t operand_count) +{ + struct avctp_pending_req *p = control->p; + struct avctp_control_req *req; + GSList *l; + + if (p && p->transaction == avctp->transaction) { + control->processed = g_slist_prepend(control->processed, p); + + if (p->timeout > 0) { + g_source_remove(p->timeout); + p->timeout = 0; + } + + control->p = NULL; + + if (control->process_id == 0) + control->process_id = g_idle_add(process_queue, + control); + } + + avctp_ref(control->session); + + for (l = control->processed; l; l = l->next) { + p = l->data; + req = p->data; + + if (p->transaction != avctp->transaction) + continue; + + if (req->func && req->func(control->session, avc->code, + avc->subunit_type, + operands, operand_count, + req->user_data)) + break; + + control->processed = g_slist_remove(control->processed, p); + pending_destroy(p, NULL); + + break; + } + + avctp_unref(control->session); +} + +static void browsing_response(struct avctp_channel *browsing, + struct avctp_header *avctp, + uint8_t *operands, + size_t operand_count) +{ + struct avctp_pending_req *p = browsing->p; + struct avctp_browsing_req *req; + GSList *l; + + if (p && p->transaction == avctp->transaction) { + browsing->processed = g_slist_prepend(browsing->processed, p); + + if (p->timeout > 0) { + g_source_remove(p->timeout); + p->timeout = 0; + } + + browsing->p = NULL; + + if (browsing->process_id == 0) + browsing->process_id = g_idle_add(process_queue, + browsing); + } + + avctp_ref(browsing->session); + + for (l = browsing->processed; l; l = l->next) { + p = l->data; + req = p->data; + + if (p->transaction != avctp->transaction) + continue; + + if (req->func && req->func(browsing->session, operands, + operand_count, req->user_data)) + break; + + browsing->processed = g_slist_remove(browsing->processed, p); + pending_destroy(p, NULL); + + break; + } + + avctp_unref(browsing->session); +} + +static gboolean session_browsing_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avctp *session = data; + struct avctp_channel *browsing = session->browsing; + uint8_t *buf = browsing->buffer; + uint8_t *operands; + struct avctp_header *avctp; + int sock, ret, packet_size, operand_count; + struct avctp_browsing_pdu_handler *handler; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto failed; + + sock = g_io_channel_unix_get_fd(chan); + + ret = read(sock, buf, browsing->imtu); + if (ret <= 0) + goto failed; + + if (ret < AVCTP_HEADER_LENGTH) { + error("Too small AVCTP packet"); + goto failed; + } + + avctp = (struct avctp_header *) buf; + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) { + error("Invalid packet type"); + goto failed; + } + + operands = buf + AVCTP_HEADER_LENGTH; + ret -= AVCTP_HEADER_LENGTH; + operand_count = ret; + + if (avctp->cr == AVCTP_RESPONSE) { + browsing_response(browsing, avctp, operands, operand_count); + return TRUE; + } + + packet_size = AVCTP_HEADER_LENGTH; + avctp->cr = AVCTP_RESPONSE; + + handler = g_slist_nth_data(browsing->handlers, 0); + if (handler == NULL) { + DBG("handler not found"); + /* FIXME: Add general reject */ + /* packet_size += avrcp_browsing_general_reject(operands); */ + goto send; + } + + ret = handler->cb(session, avctp->transaction, operands, operand_count, + handler->user_data); + if (ret < 0) { + if (ret == -EAGAIN) + return TRUE; + goto failed; + } + + packet_size += ret; + +send: + if (packet_size != 0) { + ret = write(sock, buf, packet_size); + if (ret != packet_size) + goto failed; + } + + return TRUE; + +failed: + DBG("AVCTP Browsing: disconnected"); + + if (session->browsing) { + avctp_channel_destroy(session->browsing); + session->browsing = NULL; + } + + return FALSE; +} + +static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct avctp *session = data; + struct avctp_channel *control = session->control; + uint8_t *buf = control->buffer; + uint8_t *operands, code, subunit; + struct avctp_header *avctp; + struct avc_header *avc; + int packet_size, operand_count, sock; + struct avctp_pdu_handler *handler; + ssize_t ret; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto failed; + + sock = g_io_channel_unix_get_fd(chan); + + ret = read(sock, buf, control->imtu); + if (ret <= 0) + goto failed; + + if (ret < AVCTP_HEADER_LENGTH) { + error("Too small AVCTP packet"); + goto failed; + } + + avctp = (struct avctp_header *) buf; + + ret -= AVCTP_HEADER_LENGTH; + if (ret < AVC_HEADER_LENGTH) { + error("Too small AVC packet"); + goto failed; + } + + avc = (struct avc_header *) (buf + AVCTP_HEADER_LENGTH); + + ret -= AVC_HEADER_LENGTH; + + operands = (uint8_t *) avc + AVC_HEADER_LENGTH; + operand_count = ret; + + if (avctp->cr == AVCTP_RESPONSE) { + control_response(control, avctp, avc, operands, operand_count); + return TRUE; + } + + packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH; + avctp->cr = AVCTP_RESPONSE; + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) { + avc->code = AVC_CTYPE_NOT_IMPLEMENTED; + goto done; + } + + if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { + avctp->ipid = 1; + packet_size = AVCTP_HEADER_LENGTH; + goto done; + } + + handler = find_handler(control->handlers, avc->opcode); + if (!handler) { + DBG("handler not found for 0x%02x", avc->opcode); + avc->code = AVC_CTYPE_REJECTED; + goto done; + } + + code = avc->code; + subunit = avc->subunit_type; + + ret = handler->cb(session, avctp->transaction, &code, + &subunit, operands, operand_count, + handler->user_data); + if (ret < 0) { + if (ret == -EAGAIN) + return TRUE; + goto failed; + } + + packet_size += ret; + avc->code = code; + avc->subunit_type = subunit; + +done: + ret = write(sock, buf, packet_size); + if (ret != packet_size) + goto failed; + + return TRUE; + +failed: + DBG("AVCTP session %p got disconnected", session); + avctp_shutdown(session); + return FALSE; +} + +static int uinput_create(const 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); + 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); + return err; + } + + return fd; +} + +int avctp_init_uinput(struct avctp *session, const char *name, + const char *address) +{ + if (g_str_equal(name, "Nokia CK-20W")) { + session->key_quirks[AVC_FORWARD] |= QUIRK_NO_RELEASE; + session->key_quirks[AVC_BACKWARD] |= QUIRK_NO_RELEASE; + session->key_quirks[AVC_PLAY] |= QUIRK_NO_RELEASE; + session->key_quirks[AVC_PAUSE] |= QUIRK_NO_RELEASE; + } + + session->uinput = uinput_create(address); + if (session->uinput < 0) { + error("AVCTP: failed to init uinput for %s", address); + return session->uinput; + } + + return 0; +} + +static struct avctp_channel *avctp_channel_create(struct avctp *session, int fd, + size_t imtu, size_t omtu, + avctp_destroy_cb_t destroy) +{ + struct avctp_channel *chan; + + chan = g_new0(struct avctp_channel, 1); + chan->session = session; + chan->io = g_io_channel_unix_new(fd); + chan->queue = g_queue_new(); + chan->imtu = imtu; + chan->omtu = omtu; + chan->buffer = g_malloc0(MAX(imtu, omtu)); + chan->destroy = destroy; + + return chan; +} + +static void handler_free(void *data) +{ + struct avctp_browsing_pdu_handler *handler = data; + + if (handler->destroy) + handler->destroy(handler->user_data); + + g_free(data); +} + +static void avctp_destroy_browsing(void *data) +{ + struct avctp_channel *chan = data; + + g_slist_free_full(chan->handlers, handler_free); + + chan->handlers = NULL; +} + +static struct avctp_pending_req *pending_create(struct avctp_channel *chan, + avctp_process_cb process, + void *data, + avctp_destroy_cb_t destroy) +{ + struct avctp_pending_req *p; + GSList *l, *tmp; + + if (!chan->processed) + goto done; + + tmp = g_slist_copy(chan->processed); + + /* Find first unused transaction id */ + for (l = tmp; l; l = g_slist_next(l)) { + struct avctp_pending_req *req = l->data; + + if (req->transaction == chan->transaction) { + chan->transaction++; + chan->transaction %= 16; + tmp = g_slist_delete_link(tmp, l); + l = tmp; + } + } + + g_slist_free(tmp); + +done: + p = g_new0(struct avctp_pending_req, 1); + p->chan = chan; + p->transaction = chan->transaction; + p->process = process; + p->data = data; + p->destroy = destroy; + + chan->transaction++; + chan->transaction %= 16; + + return p; +} + +static int avctp_send_req(struct avctp *session, uint8_t code, uint8_t subunit, + uint8_t opcode, const struct iovec *iov, int iov_cnt, + avctp_rsp_cb func, void *user_data) +{ + struct avctp_channel *control = session->control; + struct avctp_pending_req *p; + struct avctp_control_req *req; + struct iovec *pdu; + int i; + + if (control == NULL) + return -ENOTCONN; + + pdu = g_new0(struct iovec, iov_cnt); + + for (i = 0; i < iov_cnt; i++) { + pdu[i].iov_len = iov[i].iov_len; + pdu[i].iov_base = g_memdup(iov[i].iov_base, iov[i].iov_len); + } + + req = g_new0(struct avctp_control_req, 1); + req->code = code; + req->subunit = subunit; + req->op = opcode; + req->func = func; + req->iov = pdu; + req->iov_cnt = iov_cnt; + req->user_data = user_data; + + p = pending_create(control, process_control, req, control_req_destroy); + + req->p = p; + + g_queue_push_tail(control->queue, p); + + if (control->process_id == 0) + control->process_id = g_idle_add(process_queue, control); + + return 0; +} + +int avctp_send_browsing_req(struct avctp *session, + const struct iovec *iov, int iov_cnt, + avctp_browsing_rsp_cb func, void *user_data) +{ + struct avctp_channel *browsing = session->browsing; + struct avctp_pending_req *p; + struct avctp_browsing_req *req; + struct iovec *pdu; + int i; + + if (browsing == NULL) + return -ENOTCONN; + + pdu = g_new0(struct iovec, iov_cnt); + + for (i = 0; i < iov_cnt; i++) { + pdu[i].iov_len = iov[i].iov_len; + pdu[i].iov_base = g_memdup(iov[i].iov_base, iov[i].iov_len); + } + + req = g_new0(struct avctp_browsing_req, 1); + req->func = func; + req->iov = pdu; + req->iov_cnt = iov_cnt; + req->user_data = user_data; + + p = pending_create(browsing, process_browsing, req, + browsing_req_destroy); + + req->p = p; + + g_queue_push_tail(browsing->queue, p); + + /* Connection did not complete, delay process of the request */ + if (browsing->watch == 0) + return 0; + + if (browsing->process_id == 0) + browsing->process_id = g_idle_add(process_queue, browsing); + + return 0; +} + +int avctp_send_browsing(struct avctp *session, uint8_t transaction, + const struct iovec *iov, int iov_cnt) +{ + struct avctp_channel *browsing = session->browsing; + + if (browsing == NULL) + return -ENOTCONN; + + return avctp_browsing_send(browsing, transaction, AVCTP_RESPONSE, + iov, iov_cnt); +} + +static const char *op2str(uint8_t op) +{ + int i; + + for (i = 0; key_map[i].name != NULL; i++) { + if ((op & 0x7F) == key_map[i].avc) + return key_map[i].name; + } + + return "UNKNOWN"; +} + +static int avctp_passthrough_press(struct avctp *session, uint8_t op, + uint8_t *params, size_t params_len) +{ + struct iovec iov[2]; + int iov_cnt; + uint8_t operands[2]; + + DBG("%s", op2str(op)); + + iov[0].iov_base = operands; + iov[0].iov_len = sizeof(operands); + + /* Button pressed */ + operands[0] = op & 0x7f; + + if (params_len > 0) { + iov[1].iov_base = params; + iov[1].iov_len = params_len; + iov_cnt = 2; + operands[1] = params_len; + } else { + iov_cnt = 1; + operands[1] = 0; + } + + return avctp_send_req(session, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, + iov, iov_cnt, avctp_passthrough_rsp, NULL); +} + +static int avctp_passthrough_release(struct avctp *session, uint8_t op, + uint8_t *params, size_t params_len) +{ + struct iovec iov[2]; + int iov_cnt; + uint8_t operands[2]; + + DBG("%s", op2str(op)); + + iov[0].iov_base = operands; + iov[0].iov_len = sizeof(operands); + + /* Button released */ + operands[0] = op | 0x80; + + if (params_len > 0) { + iov[1].iov_base = params; + iov[1].iov_len = params_len; + iov_cnt = 2; + operands[1] = params_len; + } else { + iov_cnt = 1; + operands[1] = 0; + } + + return avctp_send_req(session, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, + iov, iov_cnt, NULL, NULL); +} + +static gboolean repeat_timeout(gpointer user_data) +{ + struct avctp *session = user_data; + + avctp_passthrough_release(session, session->key.op, session->key.params, + session->key.params_len); + avctp_passthrough_press(session, session->key.op, session->key.params, + session->key.params_len); + + return TRUE; +} + +static void release_pressed(struct avctp *session) +{ + avctp_passthrough_release(session, session->key.op, session->key.params, + session->key.params_len); + + if (session->key.timer > 0) + g_source_remove(session->key.timer); + + session->key.timer = 0; +} + +static bool set_pressed(struct avctp *session, uint8_t op, uint8_t *params, + size_t params_len) +{ + if (session->key.timer > 0) { + if (session->key.op == op) + return TRUE; + release_pressed(session); + } + + if (op != AVC_FAST_FORWARD && op != AVC_REWIND) + return FALSE; + + session->key.op = op; + session->key.params = params; + session->key.params_len = params_len; + session->key.timer = g_timeout_add_seconds(AVC_PRESS_TIMEOUT, + repeat_timeout, + session); + + return TRUE; +} + +static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + uint8_t *params; + size_t params_len; + + DBG("code 0x%02x operand_count %zd", code, operand_count); + + if (code != AVC_CTYPE_ACCEPTED) + return FALSE; + + if (operands[0] == AVC_VENDOR_UNIQUE) { + params = &operands[2]; + params_len = operand_count - 2; + } else { + params = NULL; + params_len = 0; + } + + if (set_pressed(session, operands[0], params, params_len)) + return FALSE; + + avctp_passthrough_release(session, operands[0], params, params_len); + + return FALSE; +} + +int avctp_send_passthrough(struct avctp *session, uint8_t op, uint8_t *params, + size_t params_len) +{ + /* Auto release if key pressed */ + if (session->key.timer > 0) + release_pressed(session); + + return avctp_passthrough_press(session, op, params, params_len); +} + +int avctp_send_vendor(struct avctp *session, uint8_t transaction, + uint8_t code, uint8_t subunit, + const struct iovec *iov, int iov_cnt) +{ + struct avctp_channel *control = session->control; + + if (control == NULL) + return -ENOTCONN; + + return avctp_send(control, transaction, AVCTP_RESPONSE, code, subunit, + AVC_OP_VENDORDEP, iov, iov_cnt); +} + +int avctp_send_vendor_req(struct avctp *session, uint8_t code, uint8_t subunit, + const struct iovec *iov, int iov_cnt, + avctp_rsp_cb func, void *user_data) +{ + return avctp_send_req(session, code, subunit, AVC_OP_VENDORDEP, iov, + iov_cnt, func, user_data); +} + +unsigned int avctp_register_passthrough_handler(struct avctp *session, + avctp_passthrough_cb cb, + void *user_data) +{ + struct avctp_channel *control = session->control; + struct avctp_passthrough_handler *handler; + static unsigned int id = 0; + + if (control == NULL || session->handler != NULL) + return 0; + + handler = g_new(struct avctp_passthrough_handler, 1); + handler->cb = cb; + handler->user_data = user_data; + handler->id = ++id; + + session->handler = handler; + + return handler->id; +} + +bool avctp_unregister_passthrough_handler(struct avctp *session, + unsigned int id) +{ + if (session->handler == NULL) + return false; + + if (session->handler->id != id) + return false; + + g_free(session->handler); + session->handler = NULL; + return true; +} + +unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, + avctp_control_pdu_cb cb, + void *user_data) +{ + struct avctp_channel *control = session->control; + struct avctp_pdu_handler *handler; + static unsigned int id = 0; + + if (control == NULL) + return 0; + + handler = find_handler(control->handlers, opcode); + if (handler) + return 0; + + handler = g_new(struct avctp_pdu_handler, 1); + handler->opcode = opcode; + handler->cb = cb; + handler->user_data = user_data; + handler->id = ++id; + + control->handlers = g_slist_append(control->handlers, handler); + + return handler->id; +} + +unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, + avctp_browsing_pdu_cb cb, + void *user_data, + avctp_destroy_cb_t destroy) +{ + struct avctp_channel *browsing = session->browsing; + struct avctp_browsing_pdu_handler *handler; + static unsigned int id = 0; + + if (browsing == NULL) + return 0; + + if (browsing->handlers != NULL) + return 0; + + handler = g_new(struct avctp_browsing_pdu_handler, 1); + handler->cb = cb; + handler->user_data = user_data; + handler->id = ++id; + handler->destroy = destroy; + + browsing->handlers = g_slist_append(browsing->handlers, handler); + + return handler->id; +} + +bool avctp_unregister_pdu_handler(struct avctp *session, unsigned int id) +{ + struct avctp_channel *control = session->control; + GSList *l; + + if (!control) + return false; + + for (l = control->handlers; l; l = g_slist_next(l)) { + struct avctp_pdu_handler *handler = l->data; + + if (handler->id != id) + continue; + + control->handlers = g_slist_remove(control->handlers, handler); + g_free(handler); + return true; + } + + return false; +} + +bool avctp_unregister_browsing_pdu_handler(struct avctp *session, + unsigned int id) +{ + struct avctp_channel *browsing = session->browsing; + GSList *l; + + if (browsing == NULL) + return false; + + for (l = browsing->handlers; l; l = g_slist_next(l)) { + struct avctp_browsing_pdu_handler *handler = l->data; + + if (handler->id != id) + continue; + + browsing->handlers = g_slist_remove(browsing->handlers, + handler); + g_free(handler); + return true; + } + + return false; +} + +struct avctp *avctp_new(int fd, size_t imtu, size_t omtu, uint16_t version) +{ + struct avctp *session; + struct avctp_channel *control; + GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + session = g_new0(struct avctp, 1); + session->version = version; + + control = avctp_channel_create(session, fd, imtu, omtu, NULL); + if (!control) { + g_free(session); + return NULL; + } + + session->uinput = -1; + session->control = control; + session->passthrough_id = avctp_register_pdu_handler(session, + AVC_OP_PASSTHROUGH, + handle_panel_passthrough, + NULL); + session->unit_id = avctp_register_pdu_handler(session, + AVC_OP_UNITINFO, + handle_unit_info, + NULL); + session->subunit_id = avctp_register_pdu_handler(session, + AVC_OP_SUBUNITINFO, + handle_subunit_info, + NULL); + + control->watch = g_io_add_watch(session->control->io, cond, + (GIOFunc) session_cb, session); + + return avctp_ref(session); +} + +int avctp_connect_browsing(struct avctp *session, int fd, size_t imtu, + size_t omtu) +{ + struct avctp_channel *browsing; + GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + if (session->browsing) + return -EISCONN; + + browsing = avctp_channel_create(session, fd, imtu, omtu, + avctp_destroy_browsing); + if (!browsing) + return -EINVAL; + + session->browsing = browsing; + browsing->watch = g_io_add_watch(session->browsing->io, cond, + (GIOFunc) session_browsing_cb, session); + + return 0; +} + +void avctp_set_destroy_cb(struct avctp *session, avctp_destroy_cb_t cb, + void *user_data) +{ + session->destroy = cb; + session->data = user_data; +} + +void avctp_shutdown(struct avctp *session) +{ + if (!session) + return; + + avctp_unref(session); +}
diff --git a/android/avctp.h b/android/avctp.h new file mode 100644 index 0000000..f0da2b3 --- /dev/null +++ b/android/avctp.h
@@ -0,0 +1,183 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AVCTP_CONTROL_PSM 23 +#define AVCTP_BROWSING_PSM 27 + +#define AVCTP_HEADER_LENGTH 3 +#define AVC_HEADER_LENGTH 3 + +#define AVC_DATA_OFFSET AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +#define AVC_DATA_MTU 512 + +/* ctype entries */ +#define AVC_CTYPE_CONTROL 0x0 +#define AVC_CTYPE_STATUS 0x1 +#define AVC_CTYPE_NOTIFY 0x3 +#define AVC_CTYPE_NOT_IMPLEMENTED 0x8 +#define AVC_CTYPE_ACCEPTED 0x9 +#define AVC_CTYPE_REJECTED 0xA +#define AVC_CTYPE_STABLE 0xC +#define AVC_CTYPE_CHANGED 0xD +#define AVC_CTYPE_INTERIM 0xF + +/* opcodes */ +#define AVC_OP_VENDORDEP 0x00 +#define AVC_OP_UNITINFO 0x30 +#define AVC_OP_SUBUNITINFO 0x31 +#define AVC_OP_PASSTHROUGH 0x7c + +/* subunits of interest */ +#define AVC_SUBUNIT_PANEL 0x09 + +/* operands in passthrough commands */ +#define AVC_SELECT 0x00 +#define AVC_UP 0x01 +#define AVC_DOWN 0x02 +#define AVC_LEFT 0x03 +#define AVC_RIGHT 0x04 +#define AVC_ROOT_MENU 0x09 +#define AVC_CONTENTS_MENU 0x0b +#define AVC_FAVORITE_MENU 0x0c +#define AVC_EXIT 0x0d +#define AVC_ON_DEMAND_MENU 0x0e +#define AVC_APPS_MENU 0x0f +#define AVC_0 0x20 +#define AVC_1 0x21 +#define AVC_2 0x22 +#define AVC_3 0x23 +#define AVC_4 0x24 +#define AVC_5 0x25 +#define AVC_6 0x26 +#define AVC_7 0x27 +#define AVC_8 0x28 +#define AVC_9 0x29 +#define AVC_DOT 0x2a +#define AVC_ENTER 0x2b +#define AVC_CHANNEL_UP 0x30 +#define AVC_CHANNEL_DOWN 0x31 +#define AVC_CHANNEL_PREVIOUS 0x32 +#define AVC_INPUT_SELECT 0x34 +#define AVC_INFO 0x35 +#define AVC_HELP 0x36 +#define AVC_PAGE_UP 0x37 +#define AVC_PAGE_DOWN 0x38 +#define AVC_LOCK 0x3a +#define AVC_POWER 0x40 +#define AVC_VOLUME_UP 0x41 +#define AVC_VOLUME_DOWN 0x42 +#define AVC_MUTE 0x43 +#define AVC_PLAY 0x44 +#define AVC_STOP 0x45 +#define AVC_PAUSE 0x46 +#define AVC_RECORD 0x47 +#define AVC_REWIND 0x48 +#define AVC_FAST_FORWARD 0x49 +#define AVC_EJECT 0x4a +#define AVC_FORWARD 0x4b +#define AVC_BACKWARD 0x4c +#define AVC_LIST 0x4d +#define AVC_F1 0x71 +#define AVC_F2 0x72 +#define AVC_F3 0x73 +#define AVC_F4 0x74 +#define AVC_F5 0x75 +#define AVC_F6 0x76 +#define AVC_F7 0x77 +#define AVC_F8 0x78 +#define AVC_F9 0x79 +#define AVC_RED 0x7a +#define AVC_GREEN 0x7b +#define AVC_BLUE 0x7c +#define AVC_YELLOW 0x7c + +#define AVC_VENDOR_UNIQUE 0x7e + +#define AVC_VENDOR_NEXT_GROUP 0x00 +#define AVC_VENDOR_PREV_GROUP 0x01 + +struct avctp; + +typedef bool (*avctp_passthrough_cb) (struct avctp *session, + uint8_t op, bool pressed, + void *user_data); +typedef ssize_t (*avctp_control_pdu_cb) (struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data); +typedef gboolean (*avctp_rsp_cb) (struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t *operands, + size_t operand_count, void *user_data); +typedef gboolean (*avctp_browsing_rsp_cb) (struct avctp *session, + uint8_t *operands, size_t operand_count, + void *user_data); +typedef ssize_t (*avctp_browsing_pdu_cb) (struct avctp *session, + uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data); + +typedef void (*avctp_destroy_cb_t) (void *user_data); + +struct avctp *avctp_new(int fd, size_t imtu, size_t omtu, uint16_t version); +void avctp_set_destroy_cb(struct avctp *session, avctp_destroy_cb_t cb, + void *user_data); + +int avctp_init_uinput(struct avctp *session, const char *name, + const char *address); +int avctp_connect_browsing(struct avctp *session, int fd, size_t imtu, + size_t omtu); + +void avctp_shutdown(struct avctp *session); + +unsigned int avctp_register_passthrough_handler(struct avctp *session, + avctp_passthrough_cb cb, + void *user_data); +bool avctp_unregister_passthrough_handler(struct avctp *session, + unsigned int id); + +unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, + avctp_control_pdu_cb cb, + void *user_data); +bool avctp_unregister_pdu_handler(struct avctp *session, unsigned int id); + +unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, + avctp_browsing_pdu_cb cb, + void *user_data, + avctp_destroy_cb_t destroy); +bool avctp_unregister_browsing_pdu_handler(struct avctp *session, + unsigned int id); + +int avctp_send_passthrough(struct avctp *session, uint8_t op, uint8_t *params, + size_t params_len); +int avctp_send_vendor(struct avctp *session, uint8_t transaction, + uint8_t code, uint8_t subunit, + const struct iovec *iov, int iov_cnt); +int avctp_send_vendor_req(struct avctp *session, uint8_t code, uint8_t subunit, + const struct iovec *iov, int iov_cnt, + avctp_rsp_cb func, void *user_data); +int avctp_send_browsing(struct avctp *session, uint8_t transaction, + const struct iovec *iov, int iov_cnt); +int avctp_send_browsing_req(struct avctp *session, + const struct iovec *iov, int iov_cnt, + avctp_browsing_rsp_cb func, void *user_data);
diff --git a/android/avdtp.c b/android/avdtp.c new file mode 100644 index 0000000..34caf3d --- /dev/null +++ b/android/avdtp.c
@@ -0,0 +1,3485 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "src/log.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "avdtp.h" +#include "../profiles/audio/a2dp-codecs.h" + +#define MAX_SEID 0x3E +static unsigned int seids; + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 +#define AVDTP_SET_CONFIGURATION 0x03 +#define AVDTP_GET_CONFIGURATION 0x04 +#define AVDTP_RECONFIGURE 0x05 +#define AVDTP_OPEN 0x06 +#define AVDTP_START 0x07 +#define AVDTP_CLOSE 0x08 +#define AVDTP_SUSPEND 0x09 +#define AVDTP_ABORT 0x0A +#define AVDTP_SECURITY_CONTROL 0x0B +#define AVDTP_GET_ALL_CAPABILITIES 0x0C +#define AVDTP_DELAY_REPORT 0x0D + +#define AVDTP_PKT_TYPE_SINGLE 0x00 +#define AVDTP_PKT_TYPE_START 0x01 +#define AVDTP_PKT_TYPE_CONTINUE 0x02 +#define AVDTP_PKT_TYPE_END 0x03 + +#define AVDTP_MSG_TYPE_COMMAND 0x00 +#define AVDTP_MSG_TYPE_GEN_REJECT 0x01 +#define AVDTP_MSG_TYPE_ACCEPT 0x02 +#define AVDTP_MSG_TYPE_REJECT 0x03 + +#define REQ_TIMEOUT 6 +#define ABORT_TIMEOUT 2 +#define DISCONNECT_TIMEOUT 1 +#define START_TIMEOUT 1 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_common_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct avdtp_single_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t no_of_packets; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t rfa0:1; + uint8_t inuse:1; + uint8_t seid:6; + uint8_t rfa2:3; + uint8_t type:1; + uint8_t media_type:4; +} __attribute__ ((packed)); + +struct seid { + uint8_t rfa0:2; + uint8_t seid:6; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_common_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct avdtp_single_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t no_of_packets; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t seid:6; + uint8_t inuse:1; + uint8_t rfa0:1; + uint8_t media_type:4; + uint8_t type:1; + uint8_t rfa2:3; +} __attribute__ ((packed)); + +struct seid { + uint8_t seid:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +/* packets */ + +struct discover_resp { + struct seid_info seps[0]; +} __attribute__ ((packed)); + +struct getcap_resp { + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct start_req { + struct seid first_seid; + struct seid other_seids[0]; +} __attribute__ ((packed)); + +struct suspend_req { + struct seid first_seid; + struct seid other_seids[0]; +} __attribute__ ((packed)); + +struct seid_rej { + uint8_t error; +} __attribute__ ((packed)); + +struct conf_rej { + uint8_t category; + uint8_t error; +} __attribute__ ((packed)); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct seid_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; +} __attribute__ ((packed)); + +struct setconf_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t rfa1:2; + uint8_t int_seid:6; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct stream_rej { + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t error; +} __attribute__ ((packed)); + +struct reconf_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; + + uint8_t serv_cap; + uint8_t serv_cap_len; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct delay_req { + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint16_t delay; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct seid_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct setconf_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; + uint8_t int_seid:6; + uint8_t rfa1:2; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct stream_rej { + uint8_t acp_seid:6; + uint8_t rfa0:2; + uint8_t error; +} __attribute__ ((packed)); + +struct reconf_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; + + uint8_t serv_cap; + uint8_t serv_cap_len; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct delay_req { + uint8_t acp_seid:6; + uint8_t rfa0:2; + uint16_t delay; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct in_buf { + gboolean active; + int no_of_packets; + uint8_t transaction; + uint8_t message_type; + uint8_t signal_id; + uint8_t buf[1024]; + uint8_t data_size; +}; + +struct pending_req { + uint8_t transaction; + uint8_t signal_id; + void *data; + size_t data_size; + struct avdtp_stream *stream; /* Set if the request targeted a stream */ + guint timeout; + gboolean collided; +}; + +struct avdtp_remote_sep { + uint8_t seid; + uint8_t type; + uint8_t media_type; + struct avdtp_service_capability *codec; + gboolean delay_reporting; + GSList *caps; /* of type struct avdtp_service_capability */ + struct avdtp_stream *stream; +}; + +struct avdtp_local_sep { + avdtp_state_t state; + struct avdtp_stream *stream; + struct seid_info info; + uint8_t codec; + uint32_t vndcodec_vendor; + uint16_t vndcodec_codec; + gboolean delay_reporting; + GSList *caps; + struct avdtp_sep_ind *ind; + struct avdtp_sep_cfm *cfm; + void *user_data; +}; + +struct stream_callback { + avdtp_stream_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct discover_callback { + unsigned int id; + avdtp_discover_cb_t cb; + void *user_data; +}; + +struct disconnect_callback { + unsigned int id; + avdtp_disconnect_cb_t cb; + void *user_data; +}; + +struct avdtp_stream { + GIOChannel *io; + uint16_t imtu; + uint16_t omtu; + struct avdtp *session; + struct avdtp_local_sep *lsep; + uint8_t rseid; + GSList *caps; + GSList *callbacks; + struct avdtp_service_capability *codec; + guint io_id; /* Transport GSource ID */ + 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 start_timer; /* Wait START command timer */ + gboolean delay_reporting; + uint16_t delay; /* AVDTP 1.3 Delay Reporting feature */ + gboolean starting; /* only valid while sep state == OPEN */ +}; + +/* Structure describing an AVDTP connection between two devices */ + +struct avdtp { + unsigned int ref; + + uint16_t version; + + guint auth_id; + + GIOChannel *io; + guint io_id; + + GSList *seps; /* Elements of type struct avdtp_remote_sep * */ + struct queue *lseps; /* Elements of type struct avdtp_local_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; + + struct discover_callback *discover; + struct pending_req *req; + + GSList *disconnect; + + bool shutdown; +}; + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, uint8_t signal_id, + void *buffer, size_t size); +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size); +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size); +static int process_queue(struct avdtp *session); +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state); + +static const char *avdtp_statestr(avdtp_state_t state) +{ + switch (state) { + case AVDTP_STATE_IDLE: + return "IDLE"; + case AVDTP_STATE_CONFIGURED: + return "CONFIGURED"; + case AVDTP_STATE_OPEN: + return "OPEN"; + case AVDTP_STATE_STREAMING: + return "STREAMING"; + case AVDTP_STATE_CLOSING: + return "CLOSING"; + case AVDTP_STATE_ABORTING: + return "ABORTING"; + default: + return "<unknown state>"; + } +} + +static gboolean try_send(int sk, void *data, size_t len) +{ + int err; + + do { + err = send(sk, data, len, 0); + } while (err < 0 && errno == EINTR); + + if (err < 0) { + error("send: %s (%d)", strerror(errno), errno); + return FALSE; + } else if ((size_t) err != len) { + error("try_send: complete buffer not sent (%d/%zu bytes)", + err, len); + return FALSE; + } + + return TRUE; +} + +static gboolean avdtp_send(struct avdtp *session, uint8_t transaction, + uint8_t message_type, uint8_t signal_id, + void *data, size_t len) +{ + unsigned int cont_fragments, sent; + struct avdtp_start_header start; + struct avdtp_continue_header cont; + int sock; + + if (session->io == NULL) { + error("avdtp_send: session is closed"); + return FALSE; + } + + sock = g_io_channel_unix_get_fd(session->io); + + /* Single packet - no fragmentation */ + if (sizeof(struct avdtp_single_header) + len <= session->omtu) { + struct avdtp_single_header single; + + memset(&single, 0, sizeof(single)); + + single.transaction = transaction; + single.packet_type = AVDTP_PKT_TYPE_SINGLE; + single.message_type = message_type; + single.signal_id = signal_id; + + memcpy(session->buf, &single, sizeof(single)); + memcpy(session->buf + sizeof(single), data, len); + + return try_send(sock, session->buf, sizeof(single) + len); + } + + /* Check if there is enough space to start packet */ + if (session->omtu < sizeof(start)) { + error("No enough space to fragment packet"); + return FALSE; + } + + /* Count the number of needed fragments */ + cont_fragments = (len - (session->omtu - sizeof(start))) / + (session->omtu - sizeof(cont)) + 1; + + DBG("%zu bytes split into %d fragments", len, cont_fragments + 1); + + /* Send the start packet */ + memset(&start, 0, sizeof(start)); + start.transaction = transaction; + start.packet_type = AVDTP_PKT_TYPE_START; + start.message_type = message_type; + start.no_of_packets = cont_fragments + 1; + start.signal_id = signal_id; + + memcpy(session->buf, &start, sizeof(start)); + memcpy(session->buf + sizeof(start), data, + session->omtu - sizeof(start)); + + if (!try_send(sock, session->buf, session->omtu)) + return FALSE; + + DBG("first packet with %zu bytes sent", session->omtu - sizeof(start)); + + sent = session->omtu - sizeof(start); + + /* Send the continue fragments and the end packet */ + while (sent < len) { + int left, to_copy; + + left = len - sent; + if (left + sizeof(cont) > session->omtu) { + cont.packet_type = AVDTP_PKT_TYPE_CONTINUE; + to_copy = session->omtu - sizeof(cont); + DBG("sending continue with %d bytes", to_copy); + } else { + cont.packet_type = AVDTP_PKT_TYPE_END; + to_copy = left; + DBG("sending end with %d bytes", to_copy); + } + + cont.transaction = transaction; + cont.message_type = message_type; + + memcpy(session->buf, &cont, sizeof(cont)); + memcpy(session->buf + sizeof(cont), data + sent, to_copy); + + if (!try_send(sock, session->buf, to_copy + sizeof(cont))) + return FALSE; + + sent += to_copy; + } + + return TRUE; +} + +static void pending_req_free(void *data) +{ + struct pending_req *req = data; + + if (req->timeout) + g_source_remove(req->timeout); + g_free(req->data); + g_free(req); +} + +static void close_stream(struct avdtp_stream *stream) +{ + int sock; + + if (stream->io == NULL) + return; + + sock = g_io_channel_unix_get_fd(stream->io); + + shutdown(sock, SHUT_RDWR); + + g_io_channel_shutdown(stream->io, FALSE, NULL); + + g_io_channel_unref(stream->io); + stream->io = NULL; +} + +static gboolean stream_close_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + DBG("Timed out waiting for peer to close the transport channel"); + + stream->timer = 0; + + close_stream(stream); + + return FALSE; +} + +static gboolean stream_open_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + DBG("Timed out waiting for peer to open the transport channel"); + + stream->timer = 0; + + stream->session->pending_open = NULL; + + avdtp_abort(stream->session, stream); + + return FALSE; +} + +void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id) +{ + err->category = category; + + if (category == AVDTP_ERRNO) + err->err.posix_errno = id; + else + err->err.error_code = id; +} + +uint8_t avdtp_error_category(struct avdtp_error *err) +{ + return err->category; +} + +int avdtp_error_error_code(struct avdtp_error *err) +{ + assert(err->category != AVDTP_ERRNO); + return err->err.error_code; +} + +int avdtp_error_posix_errno(struct avdtp_error *err) +{ + assert(err->category == AVDTP_ERRNO); + return err->err.posix_errno; +} + +static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session, + uint8_t rseid) +{ + GSList *l; + + for (l = session->streams; l != NULL; l = g_slist_next(l)) { + struct avdtp_stream *stream = l->data; + + if (stream->rseid == rseid) + return stream; + } + + return NULL; +} + +static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid) +{ + GSList *l; + + for (l = seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + + if (sep->seid == seid) + return sep; + } + + return NULL; +} + +static void stream_free(void *data) +{ + struct avdtp_stream *stream = data; + 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->start_timer > 0) + g_source_remove(stream->start_timer); + + if (stream->io) + close_stream(stream); + + if (stream->io_id) + g_source_remove(stream->io_id); + + g_slist_free_full(stream->callbacks, g_free); + g_slist_free_full(stream->caps, g_free); + + g_free(stream); +} + +static gboolean 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) + return; + + if (stream->io == NULL) + stream->io = g_io_channel_ref(io); + + stream->omtu = omtu; + stream->imtu = imtu; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) transport_cb, stream); +} + +static int pending_req_cmp(gconstpointer a, gconstpointer b) +{ + const struct pending_req *req = a; + const struct avdtp_stream *stream = b; + + if (req->stream == stream) + return 0; + + return -1; +} + +static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream) +{ + GSList *l; + struct pending_req *req; + + while ((l = g_slist_find_custom(session->prio_queue, stream, + pending_req_cmp))) { + req = l->data; + pending_req_free(req); + session->prio_queue = g_slist_remove(session->prio_queue, req); + } + + while ((l = g_slist_find_custom(session->req_queue, stream, + pending_req_cmp))) { + req = l->data; + pending_req_free(req); + session->req_queue = g_slist_remove(session->req_queue, req); + } +} + +static void handle_unanswered_req(struct avdtp *session, + struct avdtp_stream *stream) +{ + struct pending_req *req; + struct avdtp_local_sep *lsep; + struct avdtp_error err; + + if (!session->req->timeout) + /* Request is in process */ + return; + + if (session->req->signal_id == AVDTP_ABORT) { + /* Avoid freeing the Abort request here */ + DBG("handle_unanswered_req: Abort req, returning"); + session->req->stream = NULL; + return; + } + + req = session->req; + session->req = NULL; + + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + + lsep = stream->lsep; + + switch (req->signal_id) { + case AVDTP_RECONFIGURE: + error("No reply to Reconfigure request"); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_OPEN: + error("No reply to Open request"); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_START: + error("No reply to Start request"); + if (lsep && lsep->cfm && lsep->cfm->start) + lsep->cfm->start(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SUSPEND: + error("No reply to Suspend request"); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("No reply to Close request"); + if (lsep && lsep->cfm && lsep->cfm->close) + lsep->cfm->close(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SET_CONFIGURATION: + error("No reply to SetConfiguration request"); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &err, lsep->user_data); + } + + pending_req_free(req); +} + +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state) +{ + struct avdtp_stream *stream = sep->stream; + avdtp_state_t old_state; + struct avdtp_error err, *err_ptr = NULL; + GSList *l; + + if (!stream) { + error("Error changing sep state: stream not available"); + return; + } + + if (sep->state == state) { + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + DBG("stream state change failed: %s", avdtp_strerror(&err)); + err_ptr = &err; + } else { + err_ptr = NULL; + DBG("stream state changed: %s -> %s", + avdtp_statestr(sep->state), + avdtp_statestr(state)); + } + + old_state = sep->state; + sep->state = state; + + switch (state) { + case AVDTP_STATE_CONFIGURED: + if (sep->info.type == AVDTP_SEP_TYPE_SINK) + avdtp_delay_report(session, stream, stream->delay); + break; + case AVDTP_STATE_OPEN: + stream->starting = FALSE; + break; + case AVDTP_STATE_STREAMING: + if (stream->start_timer) { + g_source_remove(stream->start_timer); + stream->start_timer = 0; + } + stream->open_acp = FALSE; + break; + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + if (stream->start_timer) { + g_source_remove(stream->start_timer); + stream->start_timer = 0; + } + break; + case AVDTP_STATE_IDLE: + if (stream->start_timer) { + g_source_remove(stream->start_timer); + stream->start_timer = 0; + } + if (session->pending_open == stream) + handle_transport_connect(session, NULL, 0, 0); + if (session->req && session->req->stream == stream) + handle_unanswered_req(session, stream); + /* Remove pending commands for this stream from the queue */ + cleanup_queue(session, stream); + break; + default: + break; + } + + l = stream->callbacks; + while (l != NULL) { + struct stream_callback *cb = l->data; + l = g_slist_next(l); + cb->cb(stream, old_state, state, err_ptr, cb->user_data); + } + + if (state == AVDTP_STATE_IDLE && + g_slist_find(session->streams, stream)) { + session->streams = g_slist_remove(session->streams, stream); + stream_free(stream); + } + + if (session->io && session->shutdown && session->streams == NULL) { + int sock = g_io_channel_unix_get_fd(session->io); + shutdown(sock, SHUT_RDWR); + } +} + +static void finalize_discovery(struct avdtp *session, int err) +{ + struct discover_callback *discover = session->discover; + struct avdtp_error avdtp_err; + + if (!discover) + return; + + session->discover = NULL; + + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, err); + + if (discover->id > 0) + g_source_remove(discover->id); + + if (discover->cb) + discover->cb(session, session->seps, err ? &avdtp_err : NULL, + discover->user_data); + g_free(discover); +} + +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 sep_free(gpointer data) +{ + struct avdtp_remote_sep *sep = data; + + g_slist_free_full(sep->caps, g_free); + g_free(sep); +} + +static void avdtp_free(void *data) +{ + struct avdtp *session = data; + + DBG("%p", session); + + g_slist_free_full(session->streams, stream_free); + + if (session->io) { + g_io_channel_shutdown(session->io, FALSE, NULL); + g_io_channel_unref(session->io); + } + + if (session->io_id) { + g_source_remove(session->io_id); + session->io_id = 0; + } + + if (session->req) + pending_req_free(session->req); + + g_slist_free_full(session->req_queue, pending_req_free); + g_slist_free_full(session->prio_queue, pending_req_free); + g_slist_free_full(session->seps, sep_free); + g_slist_free_full(session->disconnect, g_free); + + /* Free copy of the SEP list */ + session->lseps = NULL; + + g_free(session->buf); + + g_free(session); +} + +static void process_disconnect(void *data) +{ + struct disconnect_callback *callback = data; + + callback->cb(callback->user_data); + + g_free(callback); +} + +static void connection_lost(struct avdtp *session, int err) +{ + DBG("Disconnected: %s (%d)", strerror(err), err); + + g_slist_foreach(session->streams, (GFunc) release_stream, session); + session->streams = NULL; + + avdtp_ref(session); + + finalize_discovery(session, err); + + g_slist_free_full(session->disconnect, process_disconnect); + session->disconnect = NULL; + + avdtp_unref(session); +} + +void avdtp_unref(struct avdtp *session) +{ + if (!session) + return; + + session->ref--; + + DBG("%p: ref=%d", session, session->ref); + + if (session->ref > 0) + return; + + finalize_discovery(session, ECONNABORTED); + + avdtp_free(session); +} + +struct avdtp *avdtp_ref(struct avdtp *session) +{ + session->ref++; + + DBG("%p: ref=%d", session, session->ref); + + return session; +} + +static bool match_by_seid(const void *data, const void *user_data) +{ + const struct avdtp_local_sep *sep = data; + uint8_t seid = PTR_TO_UINT(user_data); + + return sep->info.seid == seid; +} + +static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp *session, + uint8_t seid) +{ + return queue_find(session->lseps, match_by_seid, INT_TO_PTR(seid)); +} + +struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + struct avdtp_local_sep *lsep) +{ + GSList *l; + + if (lsep->info.inuse) + return NULL; + + for (l = session->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_data; + + /* Type must be different: source <-> sink */ + if (sep->type == lsep->info.type) + continue; + + if (sep->media_type != lsep->info.media_type) + continue; + + if (!sep->codec) + continue; + + cap = sep->codec; + codec_data = (void *) cap->data; + + if (codec_data->media_codec_type != lsep->codec) + continue; + + /* FIXME: Add Vendor Specific Codec match to SEP callback */ + if (lsep->codec == A2DP_CODEC_VENDOR) { + a2dp_vendor_codec_t *vndcodec = + (void *) codec_data->data; + + if (btohl(vndcodec->vendor_id) != lsep->vndcodec_vendor) + continue; + + if (btohs(vndcodec->codec_id) != lsep->vndcodec_codec) + continue; + } + + if (sep->stream == NULL) + return sep; + } + + return NULL; +} + +static GSList *caps_to_list(uint8_t *data, int size, + struct avdtp_service_capability **codec, + gboolean *delay_reporting) +{ + GSList *caps; + int processed; + + if (delay_reporting) + *delay_reporting = FALSE; + + for (processed = 0, caps = NULL; processed + 2 <= size;) { + struct avdtp_service_capability *cap; + uint8_t length, category; + + category = data[0]; + length = data[1]; + + if (processed + 2 + length > size) { + error("Invalid capability data in getcap resp"); + break; + } + + cap = g_malloc(sizeof(struct avdtp_service_capability) + + length); + memcpy(cap, data, 2 + length); + + processed += 2 + length; + data += 2 + length; + + caps = g_slist_append(caps, cap); + + if (category == AVDTP_MEDIA_CODEC && + length >= + sizeof(struct avdtp_media_codec_capability)) + *codec = cap; + else if (category == AVDTP_DELAY_REPORTING && delay_reporting) + *delay_reporting = TRUE; + } + + return caps; +} + +static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction, + uint8_t signal_id) +{ + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_GEN_REJECT, + signal_id, NULL, 0); +} + +static void copy_seps(void *data, void *user_data) +{ + struct avdtp_local_sep *sep = data; + struct seid_info **p = user_data; + + memcpy(*p, &sep->info, sizeof(struct seid_info)); + *p = *p + 1; +} + +static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction, + void *buf, int size) +{ + unsigned int rsp_size, sep_count; + struct seid_info *seps, *p; + gboolean ret; + + sep_count = queue_length(session->lseps); + + if (sep_count == 0) { + uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_DISCOVER, &err, sizeof(err)); + } + + rsp_size = sep_count * sizeof(struct seid_info); + + seps = g_new0(struct seid_info, sep_count); + p = seps; + + queue_foreach(session->lseps, copy_seps, &p); + + ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_DISCOVER, seps, rsp_size); + g_free(seps); + + return ret; +} + +static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size, + gboolean get_all) +{ + GSList *l, *caps; + struct avdtp_local_sep *sep = NULL; + unsigned int rsp_size; + uint8_t err, buf[1024], *ptr = buf; + uint8_t cmd; + + cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES; + + if (size < sizeof(struct seid_req)) { + err = AVDTP_BAD_LENGTH; + goto failed; + } + + sep = find_local_sep_by_seid(session, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (!sep->ind->get_capability(session, sep, &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); + } + + if (get_all && sep->delay_reporting) { + ptr[0] = AVDTP_DELAY_REPORTING; + ptr[1] = 0x00; + rsp_size += 2; + } + + g_slist_free(caps); + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd, + buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd, + &err, sizeof(err)); +} + +static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream, + struct avdtp_error *err) +{ + struct conf_rej rej; + struct avdtp_local_sep *sep; + + if (err != NULL) { + rej.error = AVDTP_UNSUPPORTED_CONFIGURATION; + rej.category = err->err.error_code; + avdtp_send(session, session->in.transaction, + AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION, + &rej, sizeof(rej)); + return; + } + + if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SET_CONFIGURATION, NULL, 0)) { + stream_free(stream); + return; + } + + sep = stream->lsep; + sep->stream = stream; + sep->info.inuse = 1; + session->streams = g_slist_append(session->streams, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); +} + +static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction, + struct setconf_req *req, unsigned int size) +{ + struct conf_rej rej; + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err, category = 0x00; + GSList *l; + + if (size < sizeof(struct setconf_req)) { + error("Too short getcap request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->stream) { + err = AVDTP_SEP_IN_USE; + goto failed; + } + + stream = g_new0(struct avdtp_stream, 1); + stream->session = session; + stream->lsep = sep; + stream->rseid = req->int_seid; + stream->caps = caps_to_list(req->caps, + size - sizeof(struct setconf_req), + &stream->codec, + &stream->delay_reporting); + + /* + * Verify that the Media Transport capability's length = 0. + * Reject otherwise + */ + for (l = stream->caps; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_TRANSPORT && + cap->length != 0) { + err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT; + goto failed_stream; + } + } + + if (stream->delay_reporting && session->version < 0x0103) + session->version = 0x0103; + + if (sep->ind && sep->ind->set_configuration) { + if (!sep->ind->set_configuration(session, sep, stream, + stream->caps, + setconf_cb, + sep->user_data)) { + err = AVDTP_UNSUPPORTED_CONFIGURATION; + category = 0x00; + goto failed_stream; + } + } else { + if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SET_CONFIGURATION, NULL, 0)) { + stream_free(stream); + return FALSE; + } + + sep->stream = stream; + sep->info.inuse = 1; + session->streams = g_slist_append(session->streams, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); + } + + return TRUE; + +failed_stream: + stream_free(stream); +failed: + rej.error = err; + rej.category = category; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_SET_CONFIGURATION, &rej, sizeof(rej)); +} + +static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + GSList *l; + struct avdtp_local_sep *sep = NULL; + int rsp_size; + uint8_t err; + uint8_t buf[1024]; + uint8_t *ptr = buf; + + if (size < (int) sizeof(struct seid_req)) { + error("Too short getconf request"); + return FALSE; + } + + memset(buf, 0, sizeof(buf)); + + sep = find_local_sep_by_seid(session, 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; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (rsp_size + cap->length + 2 > (int) sizeof(buf)) + break; + + memcpy(ptr, cap, cap->length + 2); + rsp_size += cap->length + 2; + ptr += cap->length + 2; + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_GET_CONFIGURATION, buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_GET_CONFIGURATION, &err, sizeof(err)); +} + +static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + struct conf_rej rej; + + rej.error = AVDTP_NOT_SUPPORTED_COMMAND; + rej.category = 0x00; + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_RECONFIGURE, &rej, sizeof(rej)); +} + +static void check_seid_collision(struct pending_req *req, uint8_t id) +{ + struct seid_req *seid = req->data; + + if (seid->acp_seid == id) + req->collided = TRUE; +} + +static void check_start_collision(struct pending_req *req, uint8_t id) +{ + struct start_req *start = req->data; + struct seid *seid = &start->first_seid; + int count = 1 + req->data_size - sizeof(struct start_req); + int i; + + for (i = 0; i < count; i++, seid++) { + if (seid->seid == id) { + req->collided = TRUE; + return; + } + } +} + +static void check_suspend_collision(struct pending_req *req, uint8_t id) +{ + struct suspend_req *suspend = req->data; + struct seid *seid = &suspend->first_seid; + int count = 1 + req->data_size - sizeof(struct suspend_req); + int i; + + for (i = 0; i < count; i++, seid++) { + if (seid->seid == id) { + req->collided = TRUE; + return; + } + } +} + +static void avdtp_check_collision(struct avdtp *session, uint8_t cmd, + struct avdtp_stream *stream) +{ + struct pending_req *req = session->req; + + if (req == NULL || (req->signal_id != cmd && cmd != AVDTP_ABORT)) + return; + + if (cmd == AVDTP_ABORT) + cmd = req->signal_id; + + switch (cmd) { + case AVDTP_OPEN: + case AVDTP_CLOSE: + check_seid_collision(req, stream->rseid); + break; + case AVDTP_START: + check_start_collision(req, stream->rseid); + break; + case AVDTP_SUSPEND: + check_suspend_collision(req, stream->rseid); + break; + } +} + +static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct seid_req)) { + error("Too short abort request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session, req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + 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; + } + + avdtp_check_collision(session, AVDTP_OPEN, stream); + + 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, seid->seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + /* Also reject start cmd if state is not open */ + if (sep->state != AVDTP_STATE_OPEN) { + err = AVDTP_BAD_STATE; + goto failed; + } + stream->starting = TRUE; + + if (sep->ind && sep->ind->start) { + if (!sep->ind->start(session, sep, stream, &err, + sep->user_data)) + goto failed; + } + + avdtp_check_collision(session, AVDTP_START, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_START, NULL, 0); + +failed: + DBG("Rejecting (%d)", err); + memset(&rej, 0, sizeof(rej)); + rej.acp_seid = failed_seid; + rej.error = err; + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_START, &rej, sizeof(rej)); +} + +static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct seid_req)) { + error("Too short close request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session, 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_check_collision(session, AVDTP_CLOSE, stream); + + 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, 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_check_collision(session, AVDTP_SUSPEND, stream); + + 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, req->acp_seid); + if (!sep || !sep->stream) + return TRUE; + + if (sep->ind && sep->ind->abort) + sep->ind->abort(session, sep, sep->stream, &err, + sep->user_data); + + avdtp_check_collision(session, AVDTP_ABORT, sep->stream); + + 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; +} + +static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL); +} + +static gboolean avdtp_delayreport_cmd(struct avdtp *session, + uint8_t transaction, + struct delay_req *req, + unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct delay_req)) { + error("Too short delay report request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + switch (sep->state) { + case AVDTP_STATE_IDLE: + case AVDTP_STATE_ABORTING: + case AVDTP_STATE_CLOSING: + err = AVDTP_BAD_STATE; + goto failed; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_OPEN: + case AVDTP_STATE_STREAMING: + default: + break; + } + + stream->delay = ntohs(req->delay); + + if (sep->ind && sep->ind->delayreport) { + if (!sep->ind->delayreport(session, sep, stream->rseid, + stream->delay, &err, + sep->user_data)) + goto failed; + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_DELAY_REPORT, NULL, 0); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_DELAY_REPORT, &err, sizeof(err)); +} + +static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction, + uint8_t signal_id, void *buf, int size) +{ + switch (signal_id) { + case AVDTP_DISCOVER: + DBG("Received DISCOVER_CMD"); + return avdtp_discover_cmd(session, transaction, buf, size); + case AVDTP_GET_CAPABILITIES: + DBG("Received GET_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size, + FALSE); + case AVDTP_GET_ALL_CAPABILITIES: + DBG("Received GET_ALL_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size, TRUE); + case AVDTP_SET_CONFIGURATION: + DBG("Received SET_CONFIGURATION_CMD"); + return avdtp_setconf_cmd(session, transaction, buf, size); + case AVDTP_GET_CONFIGURATION: + DBG("Received GET_CONFIGURATION_CMD"); + return avdtp_getconf_cmd(session, transaction, buf, size); + case AVDTP_RECONFIGURE: + DBG("Received RECONFIGURE_CMD"); + return avdtp_reconf_cmd(session, transaction, buf, size); + case AVDTP_OPEN: + DBG("Received OPEN_CMD"); + return avdtp_open_cmd(session, transaction, buf, size); + case AVDTP_START: + DBG("Received START_CMD"); + return avdtp_start_cmd(session, transaction, buf, size); + case AVDTP_CLOSE: + DBG("Received CLOSE_CMD"); + return avdtp_close_cmd(session, transaction, buf, size); + case AVDTP_SUSPEND: + DBG("Received SUSPEND_CMD"); + return avdtp_suspend_cmd(session, transaction, buf, size); + case AVDTP_ABORT: + DBG("Received ABORT_CMD"); + return avdtp_abort_cmd(session, transaction, buf, size); + case AVDTP_SECURITY_CONTROL: + DBG("Received SECURITY_CONTROL_CMD"); + return avdtp_secctl_cmd(session, transaction, buf, size); + case AVDTP_DELAY_REPORT: + DBG("Received DELAY_REPORT_CMD"); + return avdtp_delayreport_cmd(session, transaction, buf, size); + default: + DBG("Received unknown request id %u", signal_id); + return avdtp_unknown_cmd(session, transaction, signal_id); + } +} + +enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS }; + +static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session, + void *buf, size_t size) +{ + struct avdtp_common_header *header = buf; + struct avdtp_single_header *single = (void *) session->buf; + struct avdtp_start_header *start = (void *) session->buf; + void *payload; + gsize payload_size; + + switch (header->packet_type) { + case AVDTP_PKT_TYPE_SINGLE: + if (size < sizeof(*single)) { + error("Received too small single packet (%zu bytes)", + size); + return PARSE_ERROR; + } + if (session->in.active) { + error("SINGLE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(*single); + payload_size = size - sizeof(*single); + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.no_of_packets = 1; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.signal_id = single->signal_id; + + break; + case AVDTP_PKT_TYPE_START: + if (size < sizeof(*start)) { + error("Received too small start packet (%zu bytes)", + size); + return PARSE_ERROR; + } + if (session->in.active) { + error("START: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.no_of_packets = start->no_of_packets; + session->in.signal_id = start->signal_id; + + payload = session->buf + sizeof(*start); + payload_size = size - sizeof(*start); + + break; + case AVDTP_PKT_TYPE_CONTINUE: + if (size < sizeof(struct avdtp_continue_header)) { + error("Received too small continue packet (%zu bytes)", + size); + return PARSE_ERROR; + } + if (!session->in.active) { + error("CONTINUE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("Continue transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->in.no_of_packets <= 1) { + error("Too few continue packets"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(struct avdtp_continue_header); + payload_size = size - sizeof(struct avdtp_continue_header); + + break; + case AVDTP_PKT_TYPE_END: + if (size < sizeof(struct avdtp_continue_header)) { + error("Received too small end packet (%zu bytes)", + size); + return PARSE_ERROR; + } + if (!session->in.active) { + error("END: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("End transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->in.no_of_packets > 1) { + error("Got an end packet too early"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(struct avdtp_continue_header); + payload_size = size - sizeof(struct avdtp_continue_header); + + break; + default: + error("Invalid AVDTP packet type 0x%02X", header->packet_type); + return PARSE_ERROR; + } + + if (session->in.data_size + payload_size > + sizeof(session->in.buf)) { + error("Not enough incoming buffer space!"); + return PARSE_ERROR; + } + + memcpy(session->in.buf + session->in.data_size, payload, payload_size); + session->in.data_size += payload_size; + + if (session->in.no_of_packets > 1) { + session->in.no_of_packets--; + DBG("Received AVDTP fragment. %d to go", + session->in.no_of_packets); + return PARSE_FRAGMENT; + } + + session->in.active = FALSE; + + return PARSE_SUCCESS; +} + +static gboolean session_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp *session = data; + struct avdtp_common_header *header; + ssize_t size; + int fd; + + DBG(""); + + if (cond & G_IO_NVAL) { + session->io_id = 0; + + return FALSE; + } + + header = (void *) session->buf; + + if (cond & (G_IO_HUP | G_IO_ERR)) + goto failed; + + fd = g_io_channel_unix_get_fd(chan); + size = read(fd, session->buf, session->imtu); + if (size < 0) { + error("IO Channel read error"); + goto failed; + } + + if ((size_t) size < sizeof(struct avdtp_common_header)) { + error("Received too small packet (%zu bytes)", size); + goto failed; + } + + switch (avdtp_parse_data(session, session->buf, size)) { + case PARSE_ERROR: + goto failed; + case PARSE_FRAGMENT: + return TRUE; + case PARSE_SUCCESS: + break; + } + + /* Take a reference to protect against callback destroying session */ + avdtp_ref(session); + + 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->req && session->req->collided) { + DBG("Collision detected"); + goto next; + } + + avdtp_unref(session); + return TRUE; + } + + if (session->req == NULL) { + error("No pending request, ignoring message"); + avdtp_unref(session); + return TRUE; + } + + if (header->transaction != session->req->transaction) { + error("Transaction label doesn't match"); + avdtp_unref(session); + return TRUE; + } + + if (session->in.signal_id != session->req->signal_id) { + error("Response signal doesn't match"); + avdtp_unref(session); + return TRUE; + } + + g_source_remove(session->req->timeout); + session->req->timeout = 0; + + switch (header->message_type) { + case AVDTP_MSG_TYPE_ACCEPT: + if (!avdtp_parse_resp(session, session->req->stream, + session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse accept response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_REJECT: + if (!avdtp_parse_rej(session, session->req->stream, + session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse reject response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_GEN_REJECT: + error("Received a General Reject message"); + break; + default: + error("Unknown message type 0x%02X", header->message_type); + break; + } + +next: + pending_req_free(session->req); + session->req = NULL; + + if (session->ref > 1) + process_queue(session); + + avdtp_unref(session); + + return TRUE; + +failed: + session->io_id = 0; + connection_lost(session, EIO); + + return FALSE; +} + +static int set_priority(int fd, int priority) +{ + int err; + + err = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, + sizeof(priority)); + if (err == 0 || errno == ENOTSOCK) + return 0; + + err = -errno; + error("setsockopt(SO_PRIORITY): %s (%d)", strerror(-err), -err); + + return err; +} + +struct avdtp *avdtp_new(int fd, size_t imtu, size_t omtu, uint16_t version, + struct queue *lseps) +{ + struct avdtp *session; + GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + int new_fd; + + if (!lseps) + return NULL; + + new_fd = dup(fd); + if (new_fd < 0) { + error("dup(): %s (%d)", strerror(errno), errno); + return NULL; + } + + if (set_priority(new_fd, 6) < 0) + return NULL; + + session = g_new0(struct avdtp, 1); + session->io = g_io_channel_unix_new(new_fd); + session->version = version; + session->imtu = imtu; + session->omtu = omtu; + session->buf = g_malloc0(MAX(session->imtu, session->omtu)); + + /* 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(session->io, G_PRIORITY_LOW, cond, + (GIOFunc) session_cb, session, + NULL); + + session->lseps = lseps; + + return avdtp_ref(session); +} + +unsigned int avdtp_add_disconnect_cb(struct avdtp *session, + avdtp_disconnect_cb_t cb, + void *user_data) +{ + struct disconnect_callback *callback; + static unsigned int id = 0; + + callback = g_new0(struct disconnect_callback, 1); + callback->id = ++id; + callback->cb = cb; + callback->user_data = user_data; + session->disconnect = g_slist_append(session->disconnect, callback); + + return id; +} + +gboolean avdtp_remove_disconnect_cb(struct avdtp *session, unsigned int id) +{ + GSList *l; + + for (l = session->disconnect; l; l = g_slist_next(l)) { + struct disconnect_callback *callback = l->data; + + if (callback->id != id) + continue; + + session->disconnect = g_slist_remove(session->disconnect, + callback); + g_free(callback); + return TRUE; + } + + return FALSE; +} + +void avdtp_shutdown(struct avdtp *session) +{ + GSList *l; + bool aborting = false; + + if (!session->io) + return; + + for (l = session->streams; l; l = g_slist_next(l)) { + struct avdtp_stream *stream = l->data; + + if (stream->abort_int || + avdtp_close(session, stream, TRUE) == 0) + aborting = true; + } + + if (aborting) { + /* defer shutdown until all streams are aborted properly */ + session->shutdown = true; + } else { + int sock = g_io_channel_unix_get_fd(session->io); + + shutdown(sock, SHUT_RDWR); + } +} + +static void queue_request(struct avdtp *session, struct pending_req *req, + gboolean priority) +{ + if (priority) + session->prio_queue = g_slist_append(session->prio_queue, req); + else + session->req_queue = g_slist_append(session->req_queue, req); +} + +static uint8_t req_get_seid(struct pending_req *req) +{ + if (req->signal_id == AVDTP_DISCOVER) + return 0; + + return ((struct seid_req *) (req->data))->acp_seid; +} + +static int cancel_request(struct avdtp *session, int err) +{ + struct pending_req *req; + struct seid_req sreq; + struct avdtp_local_sep *lsep; + struct avdtp_stream *stream; + uint8_t seid; + struct avdtp_error averr; + + req = session->req; + session->req = NULL; + + avdtp_error_init(&averr, AVDTP_ERRNO, err); + + seid = req_get_seid(req); + if (seid) + stream = find_stream_by_rseid(session, seid); + else + stream = NULL; + + if (stream) { + stream->abort_int = TRUE; + lsep = stream->lsep; + } else + lsep = NULL; + + switch (req->signal_id) { + case AVDTP_RECONFIGURE: + error("Reconfigure: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_OPEN: + error("Open: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_START: + error("Start: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->start) { + lsep->cfm->start(session, lsep, stream, &averr, + lsep->user_data); + if (stream) + stream->starting = FALSE; + } + break; + case AVDTP_SUSPEND: + error("Suspend: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("Close: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->close) { + lsep->cfm->close(session, lsep, stream, &averr, + lsep->user_data); + if (stream) + stream->close_int = FALSE; + } + break; + case AVDTP_SET_CONFIGURATION: + error("SetConfiguration: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &averr, lsep->user_data); + goto failed; + case AVDTP_DISCOVER: + error("Discover: %s (%d)", strerror(err), err); + goto failed; + case AVDTP_GET_CAPABILITIES: + error("GetCapabilities: %s (%d)", strerror(err), err); + goto failed; + case AVDTP_ABORT: + error("Abort: %s (%d)", strerror(err), err); + goto failed; + } + + if (!stream) + goto failed; + + memset(&sreq, 0, sizeof(sreq)); + sreq.acp_seid = seid; + + err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq, + sizeof(sreq)); + if (err < 0) { + error("Unable to send abort request"); + goto failed; + } + + goto done; + +failed: + connection_lost(session, err); +done: + pending_req_free(req); + return err; +} + +static gboolean request_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + + cancel_request(session, ETIMEDOUT); + + return FALSE; +} + +static int send_req(struct avdtp *session, gboolean priority, + struct pending_req *req) +{ + static int transaction = 0; + int err; + + if (session->req != NULL) { + queue_request(session, req, priority); + return 0; + } + + req->transaction = transaction++; + transaction %= 16; + + /* FIXME: Should we retry to send if the buffer + was not totally sent or in case of EINTR? */ + if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND, + req->signal_id, req->data, req->data_size)) { + err = -EIO; + goto failed; + } + + session->req = req; + + req->timeout = g_timeout_add_seconds(req->signal_id == AVDTP_ABORT ? + ABORT_TIMEOUT : REQ_TIMEOUT, + request_timeout, + session); + return 0; + +failed: + g_free(req->data); + g_free(req); + return err; +} + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, uint8_t signal_id, + void *buffer, size_t size) +{ + struct pending_req *req; + + if (size > 0 && !buffer) { + DBG("Invalid buffer %p", buffer); + return -EINVAL; + } + + if (stream && stream->abort_int && signal_id != AVDTP_ABORT) { + DBG("Unable to send requests while aborting"); + return -EINVAL; + } + + req = g_new0(struct pending_req, 1); + req->signal_id = signal_id; + req->data_size = size; + req->stream = stream; + + if (size > 0) { + req->data = g_malloc(size); + memcpy(req->data, buffer, size); + } + + return send_req(session, priority, req); +} + +static gboolean avdtp_discover_resp(struct avdtp *session, + struct discover_resp *resp, int size) +{ + int sep_count, i; + uint8_t getcap_cmd; + int ret = 0; + gboolean getcap_pending = FALSE; + + if (session->version >= 0x0103) + getcap_cmd = AVDTP_GET_ALL_CAPABILITIES; + else + getcap_cmd = AVDTP_GET_CAPABILITIES; + + sep_count = size / sizeof(struct seid_info); + + for (i = 0; i < sep_count; i++) { + struct avdtp_remote_sep *sep; + struct avdtp_stream *stream; + struct seid_req req; + + DBG("seid %d type %d media %d in use %d", + resp->seps[i].seid, resp->seps[i].type, + resp->seps[i].media_type, resp->seps[i].inuse); + + stream = find_stream_by_rseid(session, resp->seps[i].seid); + + sep = find_remote_sep(session->seps, resp->seps[i].seid); + if (!sep) { + if (resp->seps[i].inuse && !stream) + continue; + sep = g_new0(struct avdtp_remote_sep, 1); + session->seps = g_slist_append(session->seps, sep); + } + + sep->stream = stream; + sep->seid = resp->seps[i].seid; + sep->type = resp->seps[i].type; + sep->media_type = resp->seps[i].media_type; + + memset(&req, 0, sizeof(req)); + req.acp_seid = sep->seid; + + ret = send_request(session, TRUE, NULL, getcap_cmd, + &req, sizeof(req)); + if (ret < 0) + break; + getcap_pending = TRUE; + } + + if (!getcap_pending) + finalize_discovery(session, -ret); + + return TRUE; +} + +static gboolean avdtp_get_capabilities_resp(struct avdtp *session, + struct getcap_resp *resp, + unsigned int size) +{ + struct avdtp_remote_sep *sep; + uint8_t seid; + + /* Check for minimum required packet size includes: + * 1. getcap resp header + * 2. media transport capability (2 bytes) + * 3. media codec capability type + length (2 bytes) + * 4. the actual media codec elements + * */ + if (size < (sizeof(struct getcap_resp) + 4 + + sizeof(struct avdtp_media_codec_capability))) { + error("Too short getcap resp packet"); + return FALSE; + } + + seid = ((struct seid_req *) session->req->data)->acp_seid; + + sep = find_remote_sep(session->seps, seid); + + DBG("seid %d type %d media %d", sep->seid, + sep->type, sep->media_type); + + if (sep->caps) { + g_slist_free_full(sep->caps, g_free); + sep->caps = NULL; + sep->codec = NULL; + sep->delay_reporting = FALSE; + } + + sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp), + &sep->codec, &sep->delay_reporting); + + return TRUE; +} + +static gboolean avdtp_set_configuration_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_single_header *resp, + int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); + + if (sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(session, sep, stream, NULL, + sep->user_data); + + 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; + + session->pending_open = stream; + + if (!stream->open_acp && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, NULL, sep->user_data); + + 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; + + /* 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); + + if (sep->cfm && sep->cfm->start) + sep->cfm->start(session, sep, stream, NULL, sep->user_data); + + return TRUE; +} + +static gboolean avdtp_close_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + close_stream(stream); + + return TRUE; +} + +static gboolean avdtp_suspend_resp(struct avdtp *session, + struct avdtp_stream *stream, + void *data, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + if (sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(session, sep, stream, NULL, sep->user_data); + + return TRUE; +} + +static gboolean avdtp_abort_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + if (sep->cfm && sep->cfm->abort) + sep->cfm->abort(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + + return TRUE; +} + +static gboolean avdtp_delay_report_resp(struct avdtp *session, + struct avdtp_stream *stream, + void *data, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->delay_report) + sep->cfm->delay_report(session, sep, stream, NULL, + sep->user_data); + + return TRUE; +} + +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size) +{ + struct pending_req *next; + const char *get_all = ""; + + if (session->prio_queue) + next = session->prio_queue->data; + else if (session->req_queue) + next = session->req_queue->data; + else + next = NULL; + + switch (signal_id) { + case AVDTP_DISCOVER: + DBG("DISCOVER request succeeded"); + return avdtp_discover_resp(session, buf, size); + case AVDTP_GET_ALL_CAPABILITIES: + get_all = "ALL_"; + /* fall through */ + case AVDTP_GET_CAPABILITIES: + DBG("GET_%sCAPABILITIES request succeeded", get_all); + if (!avdtp_get_capabilities_resp(session, buf, size)) + return FALSE; + if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES || + next->signal_id == AVDTP_GET_ALL_CAPABILITIES))) + finalize_discovery(session, 0); + return TRUE; + } + + /* The remaining commands require an existing stream so bail out + * here if the stream got unexpectedly disconnected */ + if (!stream) { + DBG("AVDTP: stream was closed while waiting for reply"); + return TRUE; + } + + switch (signal_id) { + case AVDTP_SET_CONFIGURATION: + DBG("SET_CONFIGURATION request succeeded"); + return avdtp_set_configuration_resp(session, stream, + buf, size); + case AVDTP_RECONFIGURE: + DBG("RECONFIGURE request succeeded"); + return avdtp_reconfigure_resp(session, stream, buf, size); + case AVDTP_OPEN: + DBG("OPEN request succeeded"); + return avdtp_open_resp(session, stream, buf, size); + case AVDTP_SUSPEND: + DBG("SUSPEND request succeeded"); + return avdtp_suspend_resp(session, stream, buf, size); + case AVDTP_START: + DBG("START request succeeded"); + return avdtp_start_resp(session, stream, buf, size); + case AVDTP_CLOSE: + DBG("CLOSE request succeeded"); + return avdtp_close_resp(session, stream, buf, size); + case AVDTP_ABORT: + DBG("ABORT request succeeded"); + return avdtp_abort_resp(session, stream, buf, size); + case AVDTP_DELAY_REPORT: + DBG("DELAY_REPORT request succeeded"); + return avdtp_delay_report_resp(session, stream, buf, size); + } + + error("Unknown signal id in accept response: %u", signal_id); + return TRUE; +} + +static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct seid_rej)) { + error("Too small packet for seid_rej"); + return FALSE; + } + + avdtp_error_init(err, 0x00, rej->error); + + return TRUE; +} + +static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct conf_rej)) { + error("Too small packet for conf_rej"); + return FALSE; + } + + avdtp_error_init(err, rej->category, rej->error); + + return TRUE; +} + +static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size, + struct avdtp_error *err, + uint8_t *acp_seid) +{ + if (size < sizeof(struct stream_rej)) { + error("Too small packet for stream_rej"); + return FALSE; + } + + avdtp_error_init(err, 0x00, rej->error); + + if (acp_seid) + *acp_seid = rej->acp_seid; + + return TRUE; +} + +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size) +{ + struct avdtp_error err; + uint8_t acp_seid; + struct avdtp_local_sep *sep = stream ? stream->lsep : NULL; + + switch (signal_id) { + case AVDTP_DISCOVER: + case AVDTP_GET_CAPABILITIES: + case AVDTP_GET_ALL_CAPABILITIES: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("%s request rejected: %s (%d)", + signal_id == AVDTP_DISCOVER ? "DISCOVER" : + signal_id == AVDTP_GET_CAPABILITIES ? + "GET_CAPABILITIES" : "GET_ALL_CAPABILITIES", + avdtp_strerror(&err), err.err.error_code); + if (session->discover) { + session->discover->cb(session, session->seps, &err, + session->discover->user_data); + g_free(session->discover); + session->discover = NULL; + } + return TRUE; + case AVDTP_OPEN: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("OPEN request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_SET_CONFIGURATION: + if (!conf_rej_to_err(buf, size, &err)) + return FALSE; + error("SET_CONFIGURATION request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(session, sep, stream, + &err, sep->user_data); + return TRUE; + case AVDTP_GET_CONFIGURATION: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("GET_CONFIGURATION request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->get_configuration) + sep->cfm->get_configuration(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_RECONFIGURE: + if (!conf_rej_to_err(buf, size, &err)) + return FALSE; + error("RECONFIGURE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->reconfigure) + sep->cfm->reconfigure(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_START: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("START request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->start) { + stream->starting = FALSE; + 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 FALSE; + case AVDTP_DELAY_REPORT: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("DELAY_REPORT request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->delay_report) + sep->cfm->delay_report(session, sep, stream, &err, + sep->user_data); + return TRUE; + default: + error("Unknown reject response signal id: %u", signal_id); + return TRUE; + } +} + +struct avdtp_service_capability *avdtp_stream_get_codec( + struct avdtp_stream *stream) +{ + GSList *l; + + for (l = stream->caps; l; l = l->next) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_CODEC) + return cap; + } + + return NULL; +} + +static gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, + struct avdtp_service_capability *cap) +{ + GSList *l; + struct avdtp_service_capability *stream_cap; + + for (l = stream->caps; l; l = g_slist_next(l)) { + stream_cap = l->data; + + if (stream_cap->category != cap->category || + stream_cap->length != cap->length) + continue; + + if (memcmp(stream_cap->data, cap->data, cap->length) == 0) + return TRUE; + } + + return FALSE; +} + +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps) +{ + for (; caps; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + + if (!avdtp_stream_has_capability(stream, cap)) + return FALSE; + } + + return TRUE; +} + +struct avdtp_remote_sep *avdtp_stream_get_remote_sep( + struct avdtp_stream *stream) +{ + GSList *l; + + for (l = stream->session->seps; l; l = l->next) { + struct avdtp_remote_sep *sep = l->data; + + if (sep->seid == stream->rseid) + return sep; + } + + return NULL; +} + +gboolean avdtp_stream_set_transport(struct avdtp_stream *stream, int fd, + size_t imtu, size_t omtu) +{ + GIOChannel *io; + + if (stream != stream->session->pending_open) + return FALSE; + + if (set_priority(fd, 5) < 0) + return FALSE; + + io = g_io_channel_unix_new(fd); + + handle_transport_connect(stream->session, io, imtu, omtu); + + g_io_channel_unref(io); + + 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_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) +{ + return sep->codec; +} + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + const void *data, + int length) +{ + struct avdtp_service_capability *cap; + + if (category < AVDTP_MEDIA_TRANSPORT || + category > AVDTP_DELAY_REPORTING) + return NULL; + + if (length > 0 && !data) + return NULL; + + cap = g_malloc(sizeof(struct avdtp_service_capability) + length); + cap->category = category; + cap->length = length; + + if (length > 0) + memcpy(cap->data, data, length); + + return cap; +} + +static gboolean process_discover(gpointer data) +{ + struct avdtp *session = data; + + session->discover->id = 0; + + finalize_discovery(session, 0); + + return FALSE; +} + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, + void *user_data) +{ + int err; + + if (session->discover) + return -EBUSY; + + session->discover = g_new0(struct discover_callback, 1); + + if (session->seps) { + session->discover->cb = cb; + session->discover->user_data = user_data; + session->discover->id = g_idle_add(process_discover, session); + return 0; + } + + err = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0); + if (err == 0) { + session->discover->cb = cb; + session->discover->user_data = user_data; + } + + return err; +} + +gboolean avdtp_stream_remove_cb(struct avdtp *session, + struct avdtp_stream *stream, + unsigned int id) +{ + GSList *l; + struct stream_callback *cb; + + if (!stream) + return FALSE; + + for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) { + struct stream_callback *tmp = l->data; + if (tmp && tmp->id == id) { + cb = tmp; + break; + } + } + + if (!cb) + return FALSE; + + stream->callbacks = g_slist_remove(stream->callbacks, cb); + g_free(cb); + + return TRUE; +} + +unsigned int avdtp_stream_add_cb(struct avdtp *session, + struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data) +{ + struct stream_callback *stream_cb; + static unsigned int id = 0; + + stream_cb = g_new(struct stream_callback, 1); + stream_cb->cb = cb; + stream_cb->user_data = data; + stream_cb->id = ++id; + + stream->callbacks = g_slist_append(stream->callbacks, stream_cb); + + return stream_cb->id; +} + +int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION, + &req, sizeof(req)); +} + +static void copy_capabilities(gpointer data, gpointer user_data) +{ + struct avdtp_service_capability *src_cap = data; + struct avdtp_service_capability *dst_cap; + GSList **l = user_data; + + dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data, + src_cap->length); + + *l = g_slist_append(*l, dst_cap); +} + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream) +{ + struct setconf_req *req; + struct avdtp_stream *new_stream; + unsigned char *ptr; + int err, caps_len; + struct avdtp_service_capability *cap; + GSList *l; + + if (!(lsep && rsep)) + return -EINVAL; + + DBG("%p: int_seid=%u, acp_seid=%u", session, + lsep->info.seid, rsep->seid); + + new_stream = g_new0(struct avdtp_stream, 1); + new_stream->session = session; + new_stream->lsep = lsep; + new_stream->rseid = rsep->seid; + + if (rsep->delay_reporting && lsep->delay_reporting) { + struct avdtp_service_capability *delay_reporting; + + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + caps = g_slist_append(caps, delay_reporting); + new_stream->delay_reporting = TRUE; + } + + g_slist_foreach(caps, copy_capabilities, &new_stream->caps); + + /* Calculate total size of request */ + for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { + cap = l->data; + caps_len += cap->length + 2; + } + + req = g_malloc0(sizeof(struct setconf_req) + caps_len); + + req->int_seid = lsep->info.seid; + req->acp_seid = rsep->seid; + + /* Copy the capabilities into the request */ + for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { + cap = l->data; + memcpy(ptr, cap, cap->length + 2); + ptr += cap->length + 2; + } + + err = send_request(session, FALSE, new_stream, + AVDTP_SET_CONFIGURATION, req, + sizeof(struct setconf_req) + caps_len); + if (err < 0) + stream_free(new_stream); + else { + lsep->info.inuse = 1; + lsep->stream = new_stream; + rsep->stream = new_stream; + session->streams = g_slist_append(session->streams, new_stream); + if (stream) + *stream = new_stream; + } + + g_free(req); + + return err; +} + +int avdtp_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)); +} + +static gboolean start_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + struct avdtp *session = stream->session; + + stream->open_acp = FALSE; + + if (avdtp_start(session, stream) < 0) + error("wait_timeout: avdtp_start failed"); + + stream->start_timer = 0; + + return FALSE; +} + +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) +{ + struct start_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + /* Recommendation 12: + * If the RD has configured and opened a stream it is also responsible + * to start the streaming via GAVDP_START. + */ + if (stream->open_acp) { + /* If timer already active wait it */ + if (stream->start_timer) + return 0; + + stream->start_timer = g_timeout_add_seconds(START_TIMEOUT, + start_timeout, + stream); + return 0; + } + + if (stream->close_int == TRUE) { + error("avdtp_start: rejecting start since close is initiated"); + return -EINVAL; + } + + if (stream->starting == TRUE) { + DBG("stream already started"); + return -EINPROGRESS; + } + + memset(&req, 0, sizeof(req)); + req.first_seid.seid = stream->rseid; + + ret = send_request(session, FALSE, stream, AVDTP_START, + &req, sizeof(req)); + if (ret == 0) + stream->starting = TRUE; + + return ret; +} + +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, + gboolean immediate) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->close_int == TRUE) { + error("avdtp_close: rejecting since close is already initiated"); + return -EINVAL; + } + + /* If stream is not yet in the OPEN state, let's use ABORT_CMD */ + if (stream->lsep->state < AVDTP_STATE_OPEN) + return avdtp_abort(session, stream); + + if (immediate && session->req && stream == session->req->stream) + return avdtp_abort(session, stream); + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + ret = send_request(session, FALSE, stream, AVDTP_CLOSE, + &req, sizeof(req)); + if (ret == 0) + stream->close_int = TRUE; + + return ret; +} + +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state <= AVDTP_STATE_OPEN || stream->close_int) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_SUSPEND, + &req, sizeof(req)); +} + +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state == AVDTP_STATE_ABORTING) + return -EINVAL; + + if (session->req && session->req->timeout > 0 && + stream == session->req->stream) + return cancel_request(session, ECANCELED); + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + ret = send_request(session, TRUE, stream, AVDTP_ABORT, + &req, sizeof(req)); + if (ret == 0) + stream->abort_int = TRUE; + + return ret; +} + +int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, + uint16_t delay) +{ + struct delay_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_CONFIGURED && + stream->lsep->state != AVDTP_STATE_STREAMING) + return -EINVAL; + + if (!stream->delay_reporting || session->version < 0x0103) + return -EINVAL; + + stream->delay = delay; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + req.delay = htons(delay); + + return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT, + &req, sizeof(req)); +} + +struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + gboolean delay_reporting, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data) +{ + struct avdtp_local_sep *sep; + uint8_t seid = util_get_uid(&seids, MAX_SEID); + + if (!seid) + return NULL; + + sep = g_new0(struct avdtp_local_sep, 1); + + sep->state = AVDTP_STATE_IDLE; + sep->info.seid = seid; + sep->info.type = type; + sep->info.media_type = media_type; + sep->codec = codec_type; + sep->ind = ind; + sep->cfm = cfm; + sep->user_data = user_data; + sep->delay_reporting = delay_reporting; + + DBG("SEP %p registered: type:%d codec:%d seid:%d", sep, + sep->info.type, sep->codec, sep->info.seid); + + queue_push_tail(lseps, sep); + + return sep; +} + +void avdtp_sep_set_vendor_codec(struct avdtp_local_sep *sep, uint32_t vendor_id, + uint16_t codec_id) +{ + sep->vndcodec_vendor = vendor_id; + sep->vndcodec_codec = codec_id; +} + +int avdtp_unregister_sep(struct queue *lseps, struct avdtp_local_sep *sep) +{ + if (!sep) + return -EINVAL; + + if (sep->stream) + release_stream(sep->stream, sep->stream->session); + + DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep, + sep->info.type, sep->codec, sep->info.seid); + + util_clear_uid(&seids, sep->info.seid); + queue_remove(lseps, sep); + g_free(sep); + + return 0; +} + +const char *avdtp_strerror(struct avdtp_error *err) +{ + if (err->category == AVDTP_ERRNO) + return strerror(err->err.posix_errno); + + switch (err->err.error_code) { + case AVDTP_BAD_HEADER_FORMAT: + return "Bad Header Format"; + case AVDTP_BAD_LENGTH: + return "Bad Packet Length"; + case AVDTP_BAD_ACP_SEID: + return "Bad Acceptor SEID"; + case AVDTP_SEP_IN_USE: + return "Stream End Point in Use"; + case AVDTP_SEP_NOT_IN_USE: + return "Stream End Point Not in Use"; + case AVDTP_BAD_SERV_CATEGORY: + return "Bad Service Category"; + case AVDTP_BAD_PAYLOAD_FORMAT: + return "Bad Payload format"; + case AVDTP_NOT_SUPPORTED_COMMAND: + return "Command Not Supported"; + case AVDTP_INVALID_CAPABILITIES: + return "Invalid Capabilities"; + case AVDTP_BAD_RECOVERY_TYPE: + return "Bad Recovery Type"; + case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT: + return "Bad Media Transport Format"; + case AVDTP_BAD_RECOVERY_FORMAT: + return "Bad Recovery Format"; + case AVDTP_BAD_ROHC_FORMAT: + return "Bad Header Compression Format"; + case AVDTP_BAD_CP_FORMAT: + return "Bad Content Protection Format"; + case AVDTP_BAD_MULTIPLEXING_FORMAT: + return "Bad Multiplexing Format"; + case AVDTP_UNSUPPORTED_CONFIGURATION: + return "Configuration not supported"; + case AVDTP_BAD_STATE: + return "Bad State"; + default: + return "Unknown error"; + } +} + +avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep) +{ + return sep->state; +} + +gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream) +{ + return g_slist_find(session->streams, stream) ? TRUE : FALSE; +}
diff --git a/android/avdtp.h b/android/avdtp.h new file mode 100644 index 0000000..07516a8 --- /dev/null +++ b/android/avdtp.h
@@ -0,0 +1,291 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct avdtp; +struct avdtp_stream; +struct avdtp_local_sep; +struct avdtp_remote_sep; +struct avdtp_error { + uint8_t category; + union { + uint8_t error_code; + int posix_errno; + } err; +}; + +#define AVDTP_PSM 25 + +/* SEP capability categories */ +#define AVDTP_MEDIA_TRANSPORT 0x01 +#define AVDTP_REPORTING 0x02 +#define AVDTP_RECOVERY 0x03 +#define AVDTP_CONTENT_PROTECTION 0x04 +#define AVDTP_HEADER_COMPRESSION 0x05 +#define AVDTP_MULTIPLEXING 0x06 +#define AVDTP_MEDIA_CODEC 0x07 +#define AVDTP_DELAY_REPORTING 0x08 +#define AVDTP_ERRNO 0xff + +/* AVDTP error definitions */ +#define AVDTP_BAD_HEADER_FORMAT 0x01 +#define AVDTP_BAD_LENGTH 0x11 +#define AVDTP_BAD_ACP_SEID 0x12 +#define AVDTP_SEP_IN_USE 0x13 +#define AVDTP_SEP_NOT_IN_USE 0x14 +#define AVDTP_BAD_SERV_CATEGORY 0x17 +#define AVDTP_BAD_PAYLOAD_FORMAT 0x18 +#define AVDTP_NOT_SUPPORTED_COMMAND 0x19 +#define AVDTP_INVALID_CAPABILITIES 0x1A +#define AVDTP_BAD_RECOVERY_TYPE 0x22 +#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23 +#define AVDTP_BAD_RECOVERY_FORMAT 0x25 +#define AVDTP_BAD_ROHC_FORMAT 0x26 +#define AVDTP_BAD_CP_FORMAT 0x27 +#define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28 +#define AVDTP_UNSUPPORTED_CONFIGURATION 0x29 +#define AVDTP_BAD_STATE 0x31 + +/* SEP types definitions */ +#define AVDTP_SEP_TYPE_SOURCE 0x00 +#define AVDTP_SEP_TYPE_SINK 0x01 + +/* Media types definitions */ +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 +#define AVDTP_MEDIA_TYPE_VIDEO 0x01 +#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 + +typedef enum { + AVDTP_STATE_IDLE, + AVDTP_STATE_CONFIGURED, + AVDTP_STATE_OPEN, + AVDTP_STATE_STREAMING, + AVDTP_STATE_CLOSING, + AVDTP_STATE_ABORTING, +} avdtp_state_t; + +struct avdtp_service_capability { + uint8_t category; + uint8_t length; + uint8_t data[0]; +} __attribute__ ((packed)); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_media_codec_capability { + uint8_t rfa0:4; + uint8_t media_type:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_media_codec_capability { + uint8_t media_type:4; + uint8_t rfa0:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data); + +typedef void (*avdtp_set_configuration_cb) (struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_error *err); + +/* Callbacks for when a reply is received to a command that we sent */ +struct avdtp_sep_cfm { + void (*set_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); + void (*get_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); + void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data); + void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data); + void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*reconfigure) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); +}; + +/* + * Callbacks for indicating when we received a new command. The return value + * indicates whether the command should be rejected or accepted + */ +struct avdtp_sep_ind { + gboolean (*get_capability) (struct avdtp *session, + struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err, + void *user_data); + gboolean (*set_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data); + gboolean (*get_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data); + gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*suspend) (struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + void (*abort) (struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*reconfigure) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data); + gboolean (*delayreport) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data); +}; + +typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data); +typedef void (*avdtp_disconnect_cb_t) (void *user_data); + +struct avdtp *avdtp_new(int fd, size_t imtu, size_t omtu, uint16_t version, + struct queue *lseps); + +unsigned int avdtp_add_disconnect_cb(struct avdtp *session, + avdtp_disconnect_cb_t cb, + void *user_data); +gboolean avdtp_remove_disconnect_cb(struct avdtp *session, unsigned int id); + +void avdtp_shutdown(struct avdtp *session); + +void avdtp_unref(struct avdtp *session); +struct avdtp *avdtp_ref(struct avdtp *session); + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + const void *data, + int size); + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep); + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, + void *user_data); + +gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream); + +unsigned int avdtp_stream_add_cb(struct avdtp *session, + struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data); +gboolean avdtp_stream_remove_cb(struct avdtp *session, + struct avdtp_stream *stream, + unsigned int id); + +gboolean avdtp_stream_set_transport(struct avdtp_stream *stream, int fd, + size_t imtu, size_t omtu); +gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, + uint16_t *imtu, uint16_t *omtu, + GSList **caps); +struct avdtp_service_capability *avdtp_stream_get_codec( + struct avdtp_stream *stream); +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps); +struct avdtp_remote_sep *avdtp_stream_get_remote_sep( + struct avdtp_stream *stream); + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream); + +int avdtp_get_configuration(struct avdtp *session, + struct avdtp_stream *stream); + +int avdtp_open(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, + gboolean immediate); +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, + uint16_t delay); + +struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + gboolean delay_reporting, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data); +void avdtp_sep_set_vendor_codec(struct avdtp_local_sep *sep, uint32_t vendor_id, + uint16_t codec_id); + +/* Find a matching pair of local and remote SEP ID's */ +struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + struct avdtp_local_sep *lsep); + +int avdtp_unregister_sep(struct queue *lseps, struct avdtp_local_sep *sep); + +avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep); + +void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id); +const char *avdtp_strerror(struct avdtp_error *err); +uint8_t avdtp_error_category(struct avdtp_error *err); +int avdtp_error_error_code(struct avdtp_error *err); +int avdtp_error_posix_errno(struct avdtp_error *err);
diff --git a/android/avdtptest.c b/android/avdtptest.c new file mode 100644 index 0000000..ce34443 --- /dev/null +++ b/android/avdtptest.c
@@ -0,0 +1,909 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <unistd.h> +#include <stdbool.h> +#include <errno.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "btio/btio.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "avdtp.h" + +static GMainLoop *mainloop = NULL; +static int dev_role = AVDTP_SEP_TYPE_SOURCE; +static bool preconf = false; +static struct avdtp *avdtp = NULL; +struct avdtp_stream *avdtp_stream = NULL; +struct avdtp_local_sep *local_sep = NULL; +struct avdtp_remote_sep *remote_sep = NULL; +static GIOChannel *io = NULL; +static bool reject = false; +static bdaddr_t src; +static bdaddr_t dst; +static uint16_t version = 0x0103; +static guint media_player = 0; +static guint media_recorder = 0; +static guint idle_id = 0; +static struct queue *lseps = NULL; + +static bool fragment = false; + +static enum { + CMD_GET_CONF, + CMD_OPEN, + CMD_START, + CMD_SUSPEND, + CMD_CLOSE, + CMD_ABORT, + CMD_DELAY, + CMD_NONE, +} command = CMD_NONE; + +static const char sbc_codec[] = {0x00, 0x00, 0x11, 0x15, 0x02, 0x40}; +static const char sbc_media_frame[] = { + 0x00, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x9c, 0xfd, 0x40, 0xbd, 0xde, 0xa9, 0x75, 0x43, 0x20, 0x87, 0x64, + 0x44, 0x32, 0x7f, 0xbe, 0xf7, 0x76, 0xfe, 0xf7, 0xbb, 0xbb, 0x7f, 0xbe, + 0xf7, 0x76, 0xfe, 0xf7, 0xbb, 0xbb, 0x7f, 0xbe, 0xf7, 0x76, 0xfe, 0xf7, + 0xbb, 0xbb, 0x80, 0x3e, 0xf7, 0x76, 0xfe, 0xf7, 0xbb, 0xbb, 0x83, 0x41, + 0x07, 0x77, 0x09, 0x07, 0x43, 0xb3, 0x81, 0xbc, 0xf8, 0x77, 0x02, 0xe5, + 0xa4, 0x3a, 0xa0, 0xcb, 0x38, 0xbb, 0x57, 0x90, 0xd9, 0x08, 0x9c, 0x1d, + 0x86, 0x59, 0x01, 0x0c, 0x21, 0x44, 0x68, 0x35, 0xa8, 0x57, 0x97, 0x0e, + 0x9b, 0xbb, 0x62, 0xc4, 0xca, 0x57, 0x04, 0xa1, 0xca, 0x3b, 0xa3, 0x48, + 0xd2, 0x66, 0x11, 0x33, 0x6a, 0x3b, 0xb4, 0xbb, 0x08, 0x77, 0x17, 0x03, + 0xb4, 0x3b, 0x79, 0x3b, 0x46, 0x97, 0x0e, 0xf7, 0x3d, 0xbb, 0x3d, 0x49, + 0x25, 0x86, 0x88, 0xb4, 0xad, 0x3b, 0x62, 0xbb, 0xa4, 0x47, 0x29, 0x99, + 0x3b, 0x3b, 0xaf, 0xc6, 0xd4, 0x37, 0x68, 0x94, 0x0a, 0xbb + }; + +static void parse_command(const char *cmd) +{ + if (!strncmp(cmd, "getconf", sizeof("getconf"))) { + command = CMD_GET_CONF; + } else if (!strncmp(cmd, "open", sizeof("open"))) { + command = CMD_OPEN; + } else if (!strncmp(cmd, "start", sizeof("start"))) { + command = CMD_START; + } else if (!strncmp(cmd, "suspend", sizeof("suspend"))) { + command = CMD_SUSPEND; + } else if (!strncmp(cmd, "close", sizeof("close"))) { + command = CMD_CLOSE; + } else if (!strncmp(cmd, "abort", sizeof("abort"))) { + command = CMD_ABORT; + } else if (!strncmp(cmd, "delay", sizeof("delay"))) { + command = CMD_DELAY; + } else { + printf("Unknown command '%s'\n", cmd); + printf("(getconf open start suspend close abort delay)\n"); + exit(1); + } +} + +static void send_command(void) +{ + avdtp_state_t state = avdtp_sep_get_state(local_sep); + + switch (command) { + case CMD_GET_CONF: + avdtp_get_configuration(avdtp, avdtp_stream); + break; + case CMD_OPEN: + if (state == AVDTP_STATE_CONFIGURED) + avdtp_open(avdtp, avdtp_stream); + break; + case CMD_START: + if (state == AVDTP_STATE_OPEN) + avdtp_start(avdtp, avdtp_stream); + break; + case CMD_SUSPEND: + if (state == AVDTP_STATE_STREAMING) + avdtp_suspend(avdtp , avdtp_stream); + break; + case CMD_CLOSE: + if (state == AVDTP_STATE_STREAMING) + avdtp_close(avdtp, avdtp_stream, FALSE); + break; + case CMD_ABORT: + avdtp_abort(avdtp , avdtp_stream); + break; + case CMD_DELAY: + avdtp_delay_report(avdtp , avdtp_stream , 250); + break; + case CMD_NONE: + default: + break; + } +} + +static gboolean media_writer(gpointer user_data) +{ + uint16_t omtu; + int fd; + int to_write; + + if (!avdtp_stream_get_transport(avdtp_stream, &fd, NULL, &omtu, NULL)) + return TRUE; + + if (omtu < sizeof(sbc_media_frame)) + to_write = omtu; + else + to_write = sizeof(sbc_media_frame); + + if (write(fd, sbc_media_frame, to_write) < 0) + return TRUE; + + send_command(); + + return TRUE; +} + +static bool start_media_player(void) +{ + int fd; + uint16_t omtu; + + printf("Media streaming started\n"); + + if (media_player || !avdtp_stream) + return false; + + if (!avdtp_stream_get_transport(avdtp_stream, &fd, NULL, &omtu, NULL)) + return false; + + media_player = g_timeout_add(200, media_writer, NULL); + if (!media_player) + return false; + + return true; +} + +static void stop_media_player(void) +{ + if (!media_player) + return; + + printf("Media streaming stopped\n"); + + g_source_remove(media_player); + media_player = 0; +} + +#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)); + +#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)); + +#else +#error "Unknown byte order" +#endif + +static gboolean media_reader(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + char buf[UINT16_MAX]; + struct rtp_header *rtp = (void *) buf; + static bool decode = false; + uint16_t imtu; + int fd, ret; + + if (!avdtp_stream_get_transport(avdtp_stream, &fd, &imtu, NULL, NULL)) + return TRUE; + + ret = read(fd, buf, imtu); + if (ret < 0) { + printf("Reading failed (%s)\n", strerror(errno)); + return TRUE; + } + + if (ret < (int) sizeof(*rtp)) { + printf("Not enough media data received (%u bytes)", ret); + return TRUE; + } + + if (!decode) { + printf("V=%u P=%u X=%u CC=%u M=%u PT=%u SeqNr=%d\n", + rtp->v, rtp->p, rtp->x, rtp->cc, rtp->m, rtp->pt, + be16_to_cpu(rtp->sequence_number)); + decode = true; + } + + send_command(); + + return TRUE; +} + +static bool start_media_recorder(void) +{ + int fd; + uint16_t omtu; + GIOChannel *chan; + + printf("Media recording started\n"); + + if (media_recorder || !avdtp_stream) + return false; + + if (!avdtp_stream_get_transport(avdtp_stream, &fd, NULL, &omtu, NULL)) + return false; + + chan = g_io_channel_unix_new(fd); + + media_recorder = g_io_add_watch(chan, G_IO_IN, media_reader, NULL); + g_io_channel_unref(chan); + + if (!media_recorder) + return false; + + return true; +} + +static void stop_media_recorder(void) +{ + if (!media_recorder) + return; + + printf("Media recording stopped\n"); + + g_source_remove(media_recorder); + media_recorder = 0; +} + +static void set_configuration_cfm(struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (preconf) + avdtp_open(avdtp, avdtp_stream); +} + +static void get_configuration_cfm(struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data) + { + printf("%s\n", __func__); +} + +static void disconnect_cb(void *user_data) +{ + printf("Disconnected\n"); + + g_main_loop_quit(mainloop); +} + +static void discover_cb(struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data) +{ + struct avdtp_service_capability *service; + GSList *caps = NULL; + int ret; + + remote_sep = avdtp_find_remote_sep(avdtp, local_sep); + if (!remote_sep) { + printf("Unable to find matching endpoint\n"); + avdtp_shutdown(session); + return; + } + + printf("Matching endpoint found\n"); + + service = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); + caps = g_slist_append(caps, service); + + service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, sbc_codec, + sizeof(sbc_codec)); + caps = g_slist_append(caps, service); + + ret = avdtp_set_configuration(avdtp, remote_sep, local_sep, caps, + &avdtp_stream); + + g_slist_free_full(caps, g_free); + + if (ret < 0) { + printf("Failed to set configuration (%s)\n", strerror(-ret)); + avdtp_shutdown(session); + } +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + uint16_t imtu, omtu; + GError *gerr = NULL; + int fd; + + if (err) { + printf("%s\n", err->message); + g_main_loop_quit(mainloop); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_INVALID); + if (gerr) { + printf("%s\n", gerr->message); + g_main_loop_quit(mainloop); + return; + } + + printf("Connected (imtu=%d omtu=%d)\n", imtu, omtu); + + fd = g_io_channel_unix_get_fd(chan); + + if (avdtp && avdtp_stream) { + if (!avdtp_stream_set_transport(avdtp_stream, fd, imtu, omtu)) { + printf("avdtp_stream_set_transport: failed\n"); + g_main_loop_quit(mainloop); + } + + g_io_channel_set_close_on_unref(chan, FALSE); + + send_command(); + + return; + } + + avdtp = avdtp_new(fd, imtu, omtu, version, lseps); + if (!avdtp) { + printf("Failed to create avdtp instance\n"); + g_main_loop_quit(mainloop); + return; + } + + avdtp_add_disconnect_cb(avdtp, disconnect_cb, NULL); + + if (preconf) { + int ret; + + ret = avdtp_discover(avdtp, discover_cb, NULL); + if (ret < 0) { + printf("avdtp_discover failed: %s", strerror(-ret)); + g_main_loop_quit(mainloop); + } + } +} + +static GIOChannel *do_connect(GError **err) +{ + if (fragment) + return bt_io_connect(connect_cb, NULL, NULL, err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_MTU, 48, + BT_IO_OPT_INVALID); + + return bt_io_connect(connect_cb, NULL, NULL, err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_INVALID); +} + +static void open_cfm(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + GError *gerr = NULL; + + printf("%s\n", __func__); + + do_connect(&gerr); + if (gerr) { + printf("connect failed: %s\n", gerr->message); + g_error_free(gerr); + g_main_loop_quit(mainloop); + } +} + +static void start_cfm(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + start_media_player(); + else + start_media_recorder(); +} + +static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + printf("%s\n", __func__); + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + stop_media_player(); + else + stop_media_recorder(); +} + +static void close_cfm(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + printf("%s\n", __func__); + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + stop_media_player(); + else + stop_media_recorder(); + + avdtp_stream = NULL; +} + +static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + printf("%s\n", __func__); + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + stop_media_player(); + else + stop_media_recorder(); + + avdtp_stream = NULL; +} + +static void reconfigure_cfm(struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + printf("%s\n", __func__); +} + +static void delay_report_cfm(struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + printf("%s\n", __func__); +} + +static struct avdtp_sep_cfm sep_cfm = { + .set_configuration = set_configuration_cfm, + .get_configuration = get_configuration_cfm, + .open = open_cfm, + .start = start_cfm, + .suspend = suspend_cfm, + .close = close_cfm, + .abort = abort_cfm, + .reconfigure = reconfigure_cfm, + .delay_report = delay_report_cfm, +}; + +static gboolean get_capability_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err, + void *user_data) +{ + struct avdtp_service_capability *service; + int i; + + printf("%s\n", __func__); + + if (idle_id > 0) { + g_source_remove(idle_id); + idle_id = 0; + } + + if (reject) + return FALSE; + + *caps = NULL; + + service = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); + *caps = g_slist_append(*caps, service); + + service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, sbc_codec, + sizeof(sbc_codec)); + *caps = g_slist_append(*caps, service); + + if (fragment) + for (i = 0; i < 10; i++) { + service = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, + sbc_codec, + sizeof(sbc_codec)); + *caps = g_slist_append(*caps, service); + } + + return TRUE; +} + +static gboolean set_configuration_ind(struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + if (idle_id > 0) { + g_source_remove(idle_id); + idle_id = 0; + } + + avdtp_stream = stream; + + cb(session, stream, NULL); + + send_command(); + + return TRUE; +} + +static gboolean get_configuration_ind(struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + return TRUE; +} + +static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + send_command(); + + return TRUE; +} + +static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + start_media_player(); + else + start_media_recorder(); + + send_command(); + + return TRUE; +} + +static gboolean suspend_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + stop_media_player(); + else + stop_media_recorder(); + + return TRUE; +} + +static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + stop_media_player(); + else + stop_media_recorder(); + + avdtp_stream = NULL; + + return TRUE; +} + +static void abort_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + printf("%s\n", __func__); + + if (dev_role == AVDTP_SEP_TYPE_SOURCE) + stop_media_player(); + else + stop_media_recorder(); + + avdtp_stream = NULL; +} + +static gboolean reconfigure_ind(struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + return TRUE; +} + +static gboolean delayreport_ind(struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data) +{ + printf("%s\n", __func__); + + if (reject) + return FALSE; + + return TRUE; +} + +static struct avdtp_sep_ind sep_ind = { + .get_capability = get_capability_ind, + .set_configuration = set_configuration_ind, + .get_configuration = get_configuration_ind, + .open = open_ind, + .close = close_ind, + .start = start_ind, + .suspend = suspend_ind, + .abort = abort_ind, + .reconfigure = reconfigure_ind, + .delayreport = delayreport_ind, +}; + +static void usage(void) +{ + printf("avdtptest - AVDTP testing ver %s\n", VERSION); + printf("Usage:\n" + "\tavdtptest [options]\n"); + printf("options:\n" + "\t-d <device_role> SRC (source) or SINK (sink)\n" + "\t-i <hcidev> HCI adapter\n" + "\t-c <bdaddr> connect\n" + "\t-l listen\n" + "\t-r reject commands\n" + "\t-f fragment\n" + "\t-p configure stream\n" + "\t-s <command> send command\n" + "\t-v <version> set version (0x0100, 0x0102, 0x0103\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "device_role", 1, 0, 'd' }, + { "adapter", 1, 0, 'i' }, + { "connect", 1, 0, 'c' }, + { "listen", 0, 0, 'l' }, + { "reject", 0, 0, 'r' }, + { "fragment", 0, 0, 'f' }, + { "preconf", 0, 0, 'p' }, + { "send", 1, 0, 's' }, + { "version", 1, 0, 'v' }, + { 0, 0, 0, 0 } +}; + +static GIOChannel *do_listen(GError **err) +{ + if (fragment) + return bt_io_listen(connect_cb, NULL, NULL, NULL, err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_MTU, 48, + BT_IO_OPT_INVALID); + + return bt_io_listen(connect_cb, NULL, NULL, NULL, err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); +} + +int main(int argc, char *argv[]) +{ + GError *err = NULL; + int opt; + + bacpy(&src, BDADDR_ANY); + bacpy(&dst, BDADDR_ANY); + + mainloop = g_main_loop_new(NULL, FALSE); + if (!mainloop) { + printf("Failed to create main loop\n"); + + exit(1); + } + + while ((opt = getopt_long(argc, argv, "d:hi:s:c:v:lrfp", + main_options, NULL)) != EOF) { + switch (opt) { + case 'i': + if (!strncmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &src); + else + str2ba(optarg, &src); + break; + case 'd': + if (!strncasecmp(optarg, "SRC", sizeof("SRC"))) { + dev_role = AVDTP_SEP_TYPE_SOURCE; + } else if (!strncasecmp(optarg, "SINK", + sizeof("SINK"))) { + dev_role = AVDTP_SEP_TYPE_SINK; + } else { + usage(); + exit(1); + } + break; + case 'c': + if (str2ba(optarg, &dst) < 0) { + usage(); + exit(1); + } + break; + case 'l': + bacpy(&dst, BDADDR_ANY); + break; + case 'r': + reject = true; + break; + case 'f': + fragment = true; + break; + case 'p': + preconf = true; + break; + case 's': + parse_command(optarg); + break; + case 'v': + version = strtol(optarg, NULL, 0); + if (version != 0x0100 && version != 0x0102 && + version != 0x0103) { + printf("invalid version\n"); + exit(1); + } + + break; + case 'h': + usage(); + exit(0); + default: + usage(); + exit(1); + } + } + + lseps = queue_new(); + + local_sep = avdtp_register_sep(lseps, dev_role, AVDTP_MEDIA_TYPE_AUDIO, + 0x00, TRUE, &sep_ind, &sep_cfm, NULL); + if (!local_sep) { + printf("Failed to register sep\n"); + exit(1); + } + + queue_push_tail(lseps, local_sep); + + if (!bacmp(&dst, BDADDR_ANY)) { + printf("Listening...\n"); + io = do_listen(&err); + } else { + printf("Connecting...\n"); + io = do_connect(&err); + } + + if (!io) { + printf("Failed: %s\n", err->message); + g_error_free(err); + exit(1); + } + + g_main_loop_run(mainloop); + + printf("Done\n"); + + queue_destroy(lseps, NULL); + + avdtp_unref(avdtp); + avdtp = NULL; + + g_main_loop_unref(mainloop); + mainloop = NULL; + + return 0; +}
diff --git a/android/avrcp-lib.c b/android/avrcp-lib.c new file mode 100644 index 0000000..4edfd0e --- /dev/null +++ b/android/avrcp-lib.c
@@ -0,0 +1,3615 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <glib.h> +#include <errno.h> +#include <string.h> + +#include "lib/bluetooth.h" + +#include "src/shared/util.h" +#include "src/log.h" + +#include "avctp.h" +#include "avrcp-lib.h" + + +/* Packet types */ +#define AVRCP_PACKET_TYPE_SINGLE 0x00 +#define AVRCP_PACKET_TYPE_START 0x01 +#define AVRCP_PACKET_TYPE_CONTINUING 0x02 +#define AVRCP_PACKET_TYPE_END 0x03 + +#define AVRCP_CHARSET_UTF8 0x006a + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avrcp_header { + uint8_t company_id[3]; + uint8_t pdu_id; + uint8_t packet_type:2; + uint8_t rsvd:6; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 7 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avrcp_header { + uint8_t company_id[3]; + uint8_t pdu_id; + uint8_t rsvd:6; + uint8_t packet_type:2; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 7 + +#else +#error "Unknown byte order" +#endif + +struct avrcp_browsing_header { + uint8_t pdu_id; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_BROWSING_HEADER_LENGTH 3 + +struct get_capabilities_req { + uint8_t cap; + uint8_t params[0]; +} __attribute__ ((packed)); + +struct get_capabilities_rsp { + uint8_t cap; + uint8_t number; + uint8_t params[0]; +} __attribute__ ((packed)); + +struct list_attributes_rsp { + uint8_t number; + uint8_t params[0]; +} __attribute__ ((packed)); + +struct list_values_req { + uint8_t attr; +} __attribute__ ((packed)); + +struct list_values_rsp { + uint8_t number; + uint8_t params[0]; +} __attribute__ ((packed)); + +struct get_value_req { + uint8_t number; + uint8_t attrs[0]; +} __attribute__ ((packed)); + +struct attr_value { + uint8_t attr; + uint8_t value; +} __attribute__ ((packed)); + +struct value_rsp { + uint8_t number; + struct attr_value values[0]; +} __attribute__ ((packed)); + +struct set_value_req { + uint8_t number; + struct attr_value values[0]; +} __attribute__ ((packed)); + +struct get_attribute_text_req { + uint8_t number; + uint8_t attrs[0]; +} __attribute__ ((packed)); + +struct text_value { + uint8_t attr; + uint16_t charset; + uint8_t len; + char data[0]; +} __attribute__ ((packed)); + +struct get_attribute_text_rsp { + uint8_t number; + struct text_value values[0]; +} __attribute__ ((packed)); + +struct get_value_text_req { + uint8_t attr; + uint8_t number; + uint8_t values[0]; +} __attribute__ ((packed)); + +struct get_value_text_rsp { + uint8_t number; + struct text_value values[0]; +} __attribute__ ((packed)); + +struct media_item { + uint32_t attr; + uint16_t charset; + uint16_t len; + char data[0]; +} __attribute__ ((packed)); + +struct get_element_attributes_req { + uint64_t id; + uint8_t number; + uint32_t attrs[0]; +} __attribute__ ((packed)); + +struct get_element_attributes_rsp { + uint8_t number; + struct media_item items[0]; +} __attribute__ ((packed)); + +struct get_play_status_rsp { + uint32_t duration; + uint32_t position; + uint8_t status; +} __attribute__ ((packed)); + +struct register_notification_req { + uint8_t event; + uint32_t interval; +} __attribute__ ((packed)); + +struct register_notification_rsp { + uint8_t event; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct set_volume_req { + uint8_t value; +} __attribute__ ((packed)); + +struct set_volume_rsp { + uint8_t value; +} __attribute__ ((packed)); + +struct set_addressed_req { + uint16_t id; +} __attribute__ ((packed)); + +struct set_addressed_rsp { + uint8_t status; +} __attribute__ ((packed)); + +struct set_browsed_req { + uint16_t id; +} __attribute__ ((packed)); + +struct set_browsed_rsp { + uint8_t status; + uint16_t counter; + uint32_t items; + uint16_t charset; + uint8_t depth; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct get_folder_items_req { + uint8_t scope; + uint32_t start; + uint32_t end; + uint8_t number; + uint32_t attrs[0]; +} __attribute__ ((packed)); + +struct get_folder_items_rsp { + uint8_t status; + uint16_t counter; + uint16_t number; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct change_path_req { + uint16_t counter; + uint8_t direction; + uint64_t uid; +} __attribute__ ((packed)); + +struct change_path_rsp { + uint8_t status; + uint32_t items; +} __attribute__ ((packed)); + +struct get_item_attributes_req { + uint8_t scope; + uint64_t uid; + uint16_t counter; + uint8_t number; + uint32_t attrs[0]; +} __attribute__ ((packed)); + +struct get_item_attributes_rsp { + uint8_t status; + uint8_t number; + struct media_item items[0]; +} __attribute__ ((packed)); + +struct play_item_req { + uint8_t scope; + uint64_t uid; + uint16_t counter; +} __attribute__ ((packed)); + +struct play_item_rsp { + uint8_t status; +} __attribute__ ((packed)); + +struct search_req { + uint16_t charset; + uint16_t len; + char string[0]; +} __attribute__ ((packed)); + +struct search_rsp { + uint8_t status; + uint16_t counter; + uint32_t items; +} __attribute__ ((packed)); + +struct add_to_now_playing_req { + uint8_t scope; + uint64_t uid; + uint16_t counter; +} __attribute__ ((packed)); + +struct add_to_now_playing_rsp { + uint8_t status; +} __attribute__ ((packed)); + +struct avrcp_control_handler { + uint8_t id; + uint8_t code; + uint8_t rsp; + ssize_t (*func) (struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, void *user_data); +}; + +struct avrcp_browsing_handler { + uint8_t id; + ssize_t (*func) (struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, void *user_data); +}; + +struct avrcp_continuing { + uint8_t pdu_id; + struct iovec pdu; +}; + +struct avrcp { + struct avctp *conn; + struct avrcp_player *player; + + const struct avrcp_control_handler *control_handlers; + void *control_data; + unsigned int control_id; + uint16_t control_mtu; + + struct avrcp_continuing *continuing; + + const struct avrcp_passthrough_handler *passthrough_handlers; + void *passthrough_data; + unsigned int passthrough_id; + + const struct avrcp_browsing_handler *browsing_handlers; + void *browsing_data; + unsigned int browsing_id; + + avrcp_destroy_cb_t destroy; + void *destroy_data; +}; + +struct avrcp_player { + const struct avrcp_control_ind *ind; + const struct avrcp_control_cfm *cfm; + + void *user_data; +}; + +static inline uint32_t ntoh24(const uint8_t src[3]) +{ + return src[0] << 16 | src[1] << 8 | src[2]; +} + +static inline void hton24(uint8_t dst[3], uint32_t src) +{ + dst[0] = (src & 0xff0000) >> 16; + dst[1] = (src & 0x00ff00) >> 8; + dst[2] = (src & 0x0000ff); +} + +static void continuing_free(struct avrcp_continuing *continuing) +{ + g_free(continuing->pdu.iov_base); + g_free(continuing); +} + +void avrcp_shutdown(struct avrcp *session) +{ + if (session->conn) { + if (session->control_id > 0) + avctp_unregister_pdu_handler(session->conn, + session->control_id); + if (session->passthrough_id > 0) + avctp_unregister_passthrough_handler(session->conn, + session->passthrough_id); + + if (session->browsing_id > 0) + avctp_unregister_browsing_pdu_handler(session->conn, + session->browsing_id); + + /* clear destroy callback that would call shutdown again */ + avctp_set_destroy_cb(session->conn, NULL, NULL); + avctp_shutdown(session->conn); + } + + if (session->destroy) + session->destroy(session->destroy_data); + + if (session->continuing) + continuing_free(session->continuing); + + g_free(session->player); + g_free(session); +} + +static struct avrcp_header *parse_pdu(uint8_t *operands, size_t operand_count) +{ + struct avrcp_header *pdu; + + if (!operands || operand_count < sizeof(*pdu)) { + error("AVRCP: packet too small (%zu bytes)", operand_count); + return NULL; + } + + pdu = (void *) operands; + pdu->params_len = ntohs(pdu->params_len); + + if (operand_count != pdu->params_len + sizeof(*pdu)) { + error("AVRCP: invalid parameter length (%u bytes)", + pdu->params_len); + return NULL; + } + + return pdu; +} + +static struct avrcp_browsing_header *parse_browsing_pdu(uint8_t *operands, + size_t operand_count) +{ + struct avrcp_browsing_header *pdu; + + if (!operands || operand_count < sizeof(*pdu)) { + error("AVRCP: packet too small (%zu bytes)", operand_count); + return NULL; + } + + pdu = (void *) operands; + pdu->params_len = ntohs(pdu->params_len); + + if (operand_count != pdu->params_len + sizeof(*pdu)) { + error("AVRCP: invalid parameter length (%u bytes)", + pdu->params_len); + return NULL; + } + + return pdu; +} + +static uint8_t errno2status(int err) +{ + switch (err) { + case -ENOSYS: + return AVRCP_STATUS_INVALID_COMMAND; + case -EINVAL: + return AVRCP_STATUS_INVALID_PARAM; + case 0: + return AVRCP_STATUS_SUCCESS; + case -ENOTDIR: + return AVRCP_STATUS_NOT_DIRECTORY; + case -EBADRQC: + return AVRCP_STATUS_INVALID_SCOPE; + case -ERANGE: + return AVRCP_STATUS_OUT_OF_BOUNDS; + case -ENOENT: + return AVRCP_STATUS_DOES_NOT_EXIST; + default: + return AVRCP_STATUS_INTERNAL_ERROR; + } +} + +static ssize_t handle_vendordep_pdu(struct avctp *conn, uint8_t transaction, + uint8_t *code, uint8_t *subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + const struct avrcp_control_handler *handler; + struct avrcp_header *pdu; + uint32_t company_id; + ssize_t ret; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + pdu = (void *) operands; + pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; + goto reject; + } + + company_id = ntoh24(pdu->company_id); + if (company_id != IEEEID_BTSIG) { + *code = AVC_CTYPE_NOT_IMPLEMENTED; + return 0; + } + + DBG("AVRCP PDU 0x%02X, len 0x%04X", pdu->pdu_id, pdu->params_len); + + pdu->packet_type = 0; + pdu->rsvd = 0; + + if (!session->control_handlers) + goto reject; + + for (handler = session->control_handlers; handler->id; handler++) { + if (handler->id == pdu->pdu_id) + break; + } + + if (handler->id != pdu->pdu_id || handler->code != *code) { + pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; + goto reject; + } + + if (!handler->func) { + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + goto reject; + } + + ret = handler->func(session, transaction, pdu->params_len, pdu->params, + session->control_data); + if (ret == 0) + return -EAGAIN; + + if (ret < 0) { + if (ret == -EAGAIN) + return ret; + pdu->params[0] = errno2status(ret); + goto reject; + } + + *code = handler->rsp; + pdu->params_len = htons(ret); + + return AVRCP_HEADER_LENGTH + ret; + +reject: + pdu->params_len = htons(1); + *code = AVC_CTYPE_REJECTED; + + return AVRCP_HEADER_LENGTH + 1; +} + +static bool handle_passthrough_pdu(struct avctp *conn, uint8_t op, + bool pressed, void *user_data) +{ + struct avrcp *session = user_data; + const struct avrcp_passthrough_handler *handler; + + if (!session->passthrough_handlers) + return false; + + for (handler = session->passthrough_handlers; handler->func; + handler++) { + if (handler->op == op) + break; + } + + if (handler->func == NULL) + return false; + + return handler->func(session, pressed, session->passthrough_data); +} + +static void disconnect_cb(void *data) +{ + struct avrcp *session = data; + + session->conn = NULL; + + avrcp_shutdown(session); +} + +struct avrcp *avrcp_new(int fd, size_t imtu, size_t omtu, uint16_t version) +{ + struct avrcp *session; + + session = g_new0(struct avrcp, 1); + + session->conn = avctp_new(fd, imtu, omtu, version); + if (!session->conn) { + g_free(session); + return NULL; + } + + session->passthrough_id = avctp_register_passthrough_handler( + session->conn, + handle_passthrough_pdu, + session); + session->control_id = avctp_register_pdu_handler(session->conn, + AVC_OP_VENDORDEP, + handle_vendordep_pdu, + session); + session->control_mtu = omtu - AVC_DATA_OFFSET; + + /* + * 27.1.2 AV/C Command Frame + * An AV/C command frame contains up to 512 octets of data + */ + if (session->control_mtu > AVC_DATA_MTU) + session->control_mtu = AVC_DATA_MTU; + + avctp_set_destroy_cb(session->conn, disconnect_cb, session); + + return session; +} + +static ssize_t handle_browsing_pdu(struct avctp *conn, + uint8_t transaction, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + const struct avrcp_browsing_handler *handler; + struct avrcp_browsing_header *pdu; + int ret; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + pdu = (void *) operands; + pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; + goto reject; + } + + DBG("AVRCP Browsing PDU 0x%02X, len 0x%04X", pdu->pdu_id, + pdu->params_len); + + if (!session->browsing_handlers) { + pdu->pdu_id = AVRCP_GENERAL_REJECT; + pdu->params[0] = AVRCP_STATUS_INTERNAL_ERROR; + goto reject; + } + + for (handler = session->browsing_handlers; handler->id; handler++) { + if (handler->id == pdu->pdu_id) + break; + } + + if (handler->id != pdu->pdu_id) { + pdu->pdu_id = AVRCP_GENERAL_REJECT; + pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; + goto reject; + } + + if (!handler->func) { + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + goto reject; + } + + ret = handler->func(session, transaction, pdu->params_len, pdu->params, + session->control_data); + if (ret == 0) + return -EAGAIN; + + if (ret < 0) { + if (ret == -EAGAIN) + return ret; + pdu->params[0] = errno2status(ret); + goto reject; + } + + pdu->params_len = htons(ret); + + return AVRCP_BROWSING_HEADER_LENGTH + ret; + +reject: + pdu->params_len = htons(1); + + return AVRCP_BROWSING_HEADER_LENGTH + 1; +} + +static void browsing_disconnect_cb(void *data) +{ + struct avrcp *session = data; + + session->browsing_id = 0; +} + +int avrcp_connect_browsing(struct avrcp *session, int fd, size_t imtu, + size_t omtu) +{ + int err; + + err = avctp_connect_browsing(session->conn, fd, imtu, omtu); + if (err < 0) + return err; + + session->browsing_id = avctp_register_browsing_pdu_handler( + session->conn, + handle_browsing_pdu, + session, + browsing_disconnect_cb); + + return 0; +} + +void avrcp_set_destroy_cb(struct avrcp *session, avrcp_destroy_cb_t cb, + void *user_data) +{ + session->destroy = cb; + session->destroy_data = user_data; +} + +static ssize_t get_capabilities(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_capabilities_req *req; + + if (!params || params_len != sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + switch (req->cap) { + case CAP_COMPANY_ID: + req->params[0] = 1; + hton24(&req->params[1], IEEEID_BTSIG); + return 5; + case CAP_EVENTS_SUPPORTED: + if (!player->ind || !player->ind->get_capabilities) + return -ENOSYS; + return player->ind->get_capabilities(session, transaction, + player->user_data); + } + + return -EINVAL; +} + +static ssize_t list_attributes(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + + DBG(""); + + if (!player->ind || !player->ind->list_attributes) + return -ENOSYS; + + return player->ind->list_attributes(session, transaction, + player->user_data); +} + +static bool check_attributes(uint8_t number, const uint8_t *attrs) +{ + int i; + + for (i = 0; i < number; i++) { + if (attrs[i] > AVRCP_ATTRIBUTE_LAST || + attrs[i] == AVRCP_ATTRIBUTE_ILEGAL) + return false; + } + + return true; +} + +static ssize_t get_attribute_text(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_attribute_text_req *req; + + DBG(""); + + if (!player->ind || !player->ind->get_attribute_text) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + if (params_len != sizeof(*req) + req->number) + return -EINVAL; + + if (!check_attributes(req->number, req->attrs)) + return -EINVAL; + + return player->ind->get_attribute_text(session, transaction, + req->number, req->attrs, + player->user_data); +} + +static ssize_t list_values(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct list_values_req *req; + + DBG(""); + + if (!params || params_len != sizeof(*req)) + return -EINVAL; + + req = (void *) params; + if (req->attr > AVRCP_ATTRIBUTE_LAST || + req->attr == AVRCP_ATTRIBUTE_ILEGAL) + return -EINVAL; + + if (!player->ind || !player->ind->list_values) + return -ENOSYS; + + return player->ind->list_values(session, transaction, req->attr, + player->user_data); +} + +static bool check_value(uint8_t attr, uint8_t number, const uint8_t *values) +{ + int i; + + for (i = 0; i < number; i++) { + /* Check for invalid value */ + switch (attr) { + case AVRCP_ATTRIBUTE_EQUALIZER: + if (values[i] < AVRCP_EQUALIZER_OFF || + values[i] > AVRCP_EQUALIZER_ON) + return false; + break; + case AVRCP_ATTRIBUTE_REPEAT_MODE: + if (values[i] < AVRCP_REPEAT_MODE_OFF || + values[i] > AVRCP_REPEAT_MODE_GROUP) + return false; + break; + case AVRCP_ATTRIBUTE_SHUFFLE: + if (values[i] < AVRCP_SHUFFLE_OFF || + values[i] > AVRCP_SHUFFLE_GROUP) + return false; + break; + case AVRCP_ATTRIBUTE_SCAN: + if (values[i] < AVRCP_SCAN_OFF || + values[i] > AVRCP_SCAN_GROUP) + return false; + break; + } + } + + return true; +} + +static ssize_t get_value_text(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_value_text_req *req; + + DBG(""); + + if (!player->ind || !player->ind->get_value_text) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + if (params_len != sizeof(*req) + req->number) + return -EINVAL; + + if (req->number > AVRCP_ATTRIBUTE_LAST || + req->number == AVRCP_ATTRIBUTE_ILEGAL) + return -EINVAL; + + if (!check_value(req->attr, req->number, req->values)) + return -EINVAL; + + return player->ind->get_value_text(session, transaction, params[0], + params[1], ¶ms[2], + player->user_data); +} + +static ssize_t get_value(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_value_req *req; + + DBG(""); + + if (!player->ind || !player->ind->get_value) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + if (params_len < sizeof(*req) + req->number) + return -EINVAL; + + if (!check_attributes(req->number, req->attrs)) + return -EINVAL; + + return player->ind->get_value(session, transaction, params[0], + ¶ms[1], player->user_data); +} + +static ssize_t set_value(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct set_value_req *req; + uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; + uint8_t values[AVRCP_ATTRIBUTE_LAST]; + int i; + + DBG(""); + + if (!player->ind || !player->ind->set_value) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + if (params_len < sizeof(*req) + req->number * sizeof(*req->values)) + return -EINVAL; + + for (i = 0; i < req->number; i++) { + attrs[i] = req->values[i].attr; + values[i] = req->values[i].value; + + if (!check_value(attrs[i], 1, &values[i])) + return -EINVAL; + } + + return player->ind->set_value(session, transaction, req->number, + attrs, values, player->user_data); +} + +static ssize_t get_play_status(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + + DBG(""); + + if (!player->ind || !player->ind->get_play_status) + return -ENOSYS; + + return player->ind->get_play_status(session, transaction, + player->user_data); +} + +static bool parse_attributes(uint32_t *params, uint16_t params_len, + uint8_t number, uint32_t *attrs) +{ + int i; + + for (i = 0; i < number && params_len >= sizeof(*attrs); i++, + params_len -= sizeof(*attrs)) { + attrs[i] = be32_to_cpu(params[i]); + + if (attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || + attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST) + return false; + } + + return true; +} + +static ssize_t get_element_attributes(struct avrcp *session, + uint8_t transaction, + uint16_t params_len, + uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_element_attributes_req *req; + uint64_t uid; + uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; + + DBG(""); + + if (!player->ind || !player->ind->get_element_attributes) + return -ENOSYS; + + req = (void *) params; + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + if (!parse_attributes(req->attrs, params_len - sizeof(*req), + req->number, attrs)) + return -EINVAL; + + uid = get_be64(params); + + return player->ind->get_element_attributes(session, transaction, uid, + req->number, attrs, + player->user_data); +} + +static ssize_t register_notification(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct register_notification_req *req; + uint32_t interval; + + DBG(""); + + if (!player->ind || !player->ind->register_notification) + return -ENOSYS; + + if (!params || params_len != sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + interval = be32_to_cpu(req->interval); + + return player->ind->register_notification(session, transaction, + req->event, interval, + player->user_data); +} + +static ssize_t set_volume(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct set_volume_req *req; + uint8_t volume; + + DBG(""); + + if (!player->ind || !player->ind->set_volume) + return -ENOSYS; + + if (!params || params_len != sizeof(volume)) + return -EINVAL; + + req = (void *) params; + + volume = req->value & 0x7f; + + return player->ind->set_volume(session, transaction, volume, + player->user_data); +} + +static ssize_t set_addressed(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct set_addressed_req *req; + uint16_t id; + + DBG(""); + + if (!player->ind || !player->ind->set_addressed) + return -ENOSYS; + + if (!params || params_len != sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + id = be16_to_cpu(req->id); + + return player->ind->set_addressed(session, transaction, id, + player->user_data); +} + +static void continuing_new(struct avrcp *session, uint8_t pdu_id, + const struct iovec *iov, int iov_cnt, + size_t offset) +{ + struct avrcp_continuing *continuing; + int i; + size_t len = 0; + + continuing = g_new0(struct avrcp_continuing, 1); + continuing->pdu_id = pdu_id; + + for (i = 0; i < iov_cnt; i++) { + if (i == 0 && offset) { + len += iov[i].iov_len - offset; + continue; + } + + len += iov[i].iov_len; + } + + continuing->pdu.iov_base = g_malloc0(len); + + DBG("len %zu", len); + + for (i = 0; i < iov_cnt; i++) { + if (i == 0 && offset) { + memcpy(continuing->pdu.iov_base, + iov[i].iov_base + offset, + iov[i].iov_len - offset); + continuing->pdu.iov_len += iov[i].iov_len - offset; + continue; + } + + memcpy(continuing->pdu.iov_base + continuing->pdu.iov_len, + iov[i].iov_base, iov[i].iov_len); + continuing->pdu.iov_len += iov[i].iov_len; + } + + session->continuing = continuing; +} + +static int avrcp_send_internal(struct avrcp *session, uint8_t transaction, + uint8_t code, uint8_t subunit, + uint8_t pdu_id, uint8_t type, + const struct iovec *iov, int iov_cnt) +{ + struct iovec pdu[iov_cnt + 1]; + struct avrcp_header hdr; + int i; + + /* + * If a receiver receives a start fragment or non-fragmented AVRCP + * Specific AV/C message when it already has an incomplete fragment + * from that sender then the receiver shall consider the first PDU + * aborted. + */ + if (session->continuing) { + continuing_free(session->continuing); + session->continuing = NULL; + } + + memset(&hdr, 0, sizeof(hdr)); + + pdu[0].iov_base = &hdr; + pdu[0].iov_len = sizeof(hdr); + + hdr.packet_type = type; + + for (i = 0; i < iov_cnt; i++) { + pdu[i + 1].iov_base = iov[i].iov_base; + + if (pdu[0].iov_len + hdr.params_len + iov[i].iov_len <= + session->control_mtu) { + pdu[i + 1].iov_len = iov[i].iov_len; + hdr.params_len += iov[i].iov_len; + if (hdr.packet_type != AVRCP_PACKET_TYPE_SINGLE) + hdr.packet_type = AVRCP_PACKET_TYPE_END; + continue; + } + + /* + * Only send what can fit and store the remaining in the + * continuing iovec + */ + pdu[i + 1].iov_len = session->control_mtu - + (pdu[0].iov_len + hdr.params_len); + hdr.params_len += pdu[i + 1].iov_len; + + continuing_new(session, pdu_id, &iov[i], iov_cnt - i, + pdu[i + 1].iov_len); + + hdr.packet_type = hdr.packet_type != AVRCP_PACKET_TYPE_SINGLE ? + AVRCP_PACKET_TYPE_CONTINUING : + AVRCP_PACKET_TYPE_START; + break; + } + + hton24(hdr.company_id, IEEEID_BTSIG); + hdr.pdu_id = pdu_id; + hdr.params_len = htons(hdr.params_len); + + return avctp_send_vendor(session->conn, transaction, code, subunit, + pdu, iov_cnt + 1); +} + +static ssize_t request_continuing(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct iovec iov; + int err; + + DBG(""); + + if (!params || params_len != 1 || !session->continuing || + session->continuing->pdu_id != params[0]) + return -EINVAL; + + iov.iov_base = session->continuing->pdu.iov_base; + iov.iov_len = session->continuing->pdu.iov_len; + + DBG("len %zu", iov.iov_len); + + session->continuing->pdu.iov_base = NULL; + + err = avrcp_send_internal(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, params[0], + AVRCP_PACKET_TYPE_CONTINUING, &iov, 1); + + g_free(iov.iov_base); + + if (err < 0) + return -EINVAL; + + return 0; +} + +static ssize_t abort_continuing(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + DBG(""); + + if (!params || params_len != 1 || !session->continuing) + return -EINVAL; + + continuing_free(session->continuing); + session->continuing = NULL; + + avrcp_send_internal(session, transaction, AVC_CTYPE_ACCEPTED, + AVC_SUBUNIT_PANEL, AVRCP_ABORT_CONTINUING, + AVRCP_PACKET_TYPE_SINGLE, NULL, 0); + + return 0; +} + +static const struct avrcp_control_handler player_handlers[] = { + { AVRCP_GET_CAPABILITIES, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + get_capabilities }, + { AVRCP_LIST_PLAYER_ATTRIBUTES, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + list_attributes }, + { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + get_attribute_text }, + { AVRCP_LIST_PLAYER_VALUES, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + list_values }, + { AVRCP_GET_PLAYER_VALUE_TEXT, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + get_value_text }, + { AVRCP_GET_CURRENT_PLAYER_VALUE, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + get_value }, + { AVRCP_SET_PLAYER_VALUE, + AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, + set_value }, + { AVRCP_GET_PLAY_STATUS, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + get_play_status }, + { AVRCP_GET_ELEMENT_ATTRIBUTES, + AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, + get_element_attributes }, + { AVRCP_REGISTER_NOTIFICATION, + AVC_CTYPE_NOTIFY, AVC_CTYPE_INTERIM, + register_notification }, + { AVRCP_SET_ABSOLUTE_VOLUME, + AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, + set_volume }, + { AVRCP_SET_ADDRESSED_PLAYER, + AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, + set_addressed }, + { AVRCP_REQUEST_CONTINUING, + AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, + request_continuing }, + { AVRCP_ABORT_CONTINUING, + AVC_CTYPE_CONTROL, AVC_CTYPE_ACCEPTED, + abort_continuing }, + { }, +}; + +static void avrcp_set_control_handlers(struct avrcp *session, + const struct avrcp_control_handler *handlers, + void *user_data) +{ + session->control_handlers = handlers; + session->control_data = user_data; +} + +static ssize_t set_browsed(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct set_browsed_req *req; + uint16_t id; + + DBG(""); + + if (!player->ind || !player->ind->set_browsed) + return -ENOSYS; + + if (!params || params_len != sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + id = be16_to_cpu(req->id); + + return player->ind->set_browsed(session, transaction, id, + player->user_data); +} + +static ssize_t get_folder_items(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_folder_items_req *req; + uint32_t start, end; + uint16_t number; + uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; + int i; + + DBG(""); + + if (!player->ind || !player->ind->get_folder_items) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + if (req->scope > AVRCP_MEDIA_NOW_PLAYING) + return -EBADRQC; + + start = be32_to_cpu(req->start); + end = be32_to_cpu(req->end); + + if (start > end) + return -ERANGE; + + number = be16_to_cpu(req->number); + + for (i = 0; i < number; i++) { + attrs[i] = be32_to_cpu(req->attrs[i]); + + if (attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || + attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EINVAL; + } + + return player->ind->get_folder_items(session, transaction, req->scope, + start, end, number, attrs, + player->user_data); +} + +static ssize_t change_path(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct change_path_req *req; + uint16_t counter; + uint64_t uid; + + DBG(""); + + if (!player->ind || !player->ind->change_path) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + counter = be16_to_cpu(req->counter); + uid = be64_to_cpu(req->uid); + + return player->ind->change_path(session, transaction, counter, + req->direction, uid, player->user_data); +} + +static ssize_t get_item_attributes(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct get_item_attributes_req *req; + uint64_t uid; + uint16_t counter; + uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; + int i; + + DBG(""); + + if (!player->ind || !player->ind->get_item_attributes) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + if (req->scope > AVRCP_MEDIA_NOW_PLAYING) + return -EBADRQC; + + uid = be64_to_cpu(req->uid); + counter = be16_to_cpu(req->counter); + + for (i = 0; i < req->number; i++) { + attrs[i] = be32_to_cpu(req->attrs[i]); + + if (attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || + attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EINVAL; + } + + return player->ind->get_item_attributes(session, transaction, + req->scope, uid, counter, + req->number, attrs, + player->user_data); +} + +static ssize_t play_item(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct play_item_req *req; + uint64_t uid; + uint16_t counter; + + DBG(""); + + if (!player->ind || !player->ind->play_item) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + if (req->scope > AVRCP_MEDIA_NOW_PLAYING) + return -EBADRQC; + + uid = be64_to_cpu(req->uid); + counter = be16_to_cpu(req->counter); + + return player->ind->play_item(session, transaction, req->scope, uid, + counter, player->user_data); +} + +static ssize_t search(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct search_req *req; + char *string; + uint16_t len; + int ret; + + DBG(""); + + if (!player->ind || !player->ind->search) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + len = be16_to_cpu(req->len); + if (!len) + return -EINVAL; + + string = strndup(req->string, len); + + ret = player->ind->search(session, transaction, string, + player->user_data); + + free(string); + + return ret; +} + +static ssize_t add_to_now_playing(struct avrcp *session, uint8_t transaction, + uint16_t params_len, uint8_t *params, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct add_to_now_playing_req *req; + uint64_t uid; + uint16_t counter; + + DBG(""); + + if (!player->ind || !player->ind->add_to_now_playing) + return -ENOSYS; + + if (!params || params_len < sizeof(*req)) + return -EINVAL; + + req = (void *) params; + + if (req->scope > AVRCP_MEDIA_NOW_PLAYING) + return -EBADRQC; + + uid = be64_to_cpu(req->uid); + counter = be16_to_cpu(req->counter); + + return player->ind->add_to_now_playing(session, transaction, req->scope, + uid, counter, + player->user_data); +} + +static const struct avrcp_browsing_handler browsing_handlers[] = { + { AVRCP_SET_BROWSED_PLAYER, set_browsed }, + { AVRCP_GET_FOLDER_ITEMS, get_folder_items }, + { AVRCP_CHANGE_PATH, change_path }, + { AVRCP_GET_ITEM_ATTRIBUTES, get_item_attributes }, + { AVRCP_PLAY_ITEM, play_item }, + { AVRCP_SEARCH, search }, + { AVRCP_ADD_TO_NOW_PLAYING, add_to_now_playing }, + { }, +}; + +static void avrcp_set_browsing_handlers(struct avrcp *session, + const struct avrcp_browsing_handler *handlers, + void *user_data) +{ + session->browsing_handlers = handlers; + session->browsing_data = user_data; +} + +void avrcp_register_player(struct avrcp *session, + const struct avrcp_control_ind *ind, + const struct avrcp_control_cfm *cfm, + void *user_data) +{ + struct avrcp_player *player; + + player = g_new0(struct avrcp_player, 1); + player->ind = ind; + player->cfm = cfm; + player->user_data = user_data; + + avrcp_set_control_handlers(session, player_handlers, player); + avrcp_set_browsing_handlers(session, browsing_handlers, player); + session->player = player; +} + +void avrcp_set_passthrough_handlers(struct avrcp *session, + const struct avrcp_passthrough_handler *handlers, + void *user_data) +{ + session->passthrough_handlers = handlers; + session->passthrough_data = user_data; +} + +int avrcp_init_uinput(struct avrcp *session, const char *name, + const char *address) +{ + return avctp_init_uinput(session->conn, name, address); +} + +int avrcp_send(struct avrcp *session, uint8_t transaction, uint8_t code, + uint8_t subunit, uint8_t pdu_id, + const struct iovec *iov, int iov_cnt) +{ + return avrcp_send_internal(session, transaction, code, subunit, pdu_id, + AVRCP_PACKET_TYPE_SINGLE, iov, iov_cnt); +} + +static int status2errno(uint8_t status) +{ + switch (status) { + case AVRCP_STATUS_INVALID_COMMAND: + return -ENOSYS; + case AVRCP_STATUS_INVALID_PARAM: + return -EINVAL; + case AVRCP_STATUS_SUCCESS: + return 0; + case AVRCP_STATUS_NOT_DIRECTORY: + return -ENOTDIR; + case AVRCP_STATUS_INVALID_SCOPE: + return -EBADRQC; + case AVRCP_STATUS_OUT_OF_BOUNDS: + return -ERANGE; + case AVRCP_STATUS_INTERNAL_ERROR: + case AVRCP_STATUS_INVALID_PLAYER_ID: + case AVRCP_STATUS_PLAYER_NOT_BROWSABLE: + case AVRCP_STATUS_NO_AVAILABLE_PLAYERS: + case AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED: + return -EPERM; + default: + return -EPROTO; + } +} + +static int parse_status(struct avrcp_header *pdu) +{ + if (pdu->params_len < 1) + return -EPROTO; + + return status2errno(pdu->params[0]); +} + +static int parse_browsing_status(struct avrcp_browsing_header *pdu) +{ + if (pdu->params_len < 1) + return -EPROTO; + + return status2errno(pdu->params[0]); +} + +static int avrcp_send_req(struct avrcp *session, uint8_t code, uint8_t subunit, + uint8_t pdu_id, const struct iovec *iov, + int iov_cnt, avctp_rsp_cb func, + void *user_data) +{ + struct iovec pdu[iov_cnt + 1]; + struct avrcp_header hdr; + int i; + + memset(&hdr, 0, sizeof(hdr)); + + pdu[0].iov_base = &hdr; + pdu[0].iov_len = sizeof(hdr); + + for (i = 0; i < iov_cnt; i++) { + pdu[i + 1].iov_base = iov[i].iov_base; + pdu[i + 1].iov_len = iov[i].iov_len; + hdr.params_len += iov[i].iov_len; + } + + hton24(hdr.company_id, IEEEID_BTSIG); + hdr.pdu_id = pdu_id; + hdr.packet_type = AVRCP_PACKET_TYPE_SINGLE; + hdr.params_len = htons(hdr.params_len); + + return avctp_send_vendor_req(session->conn, code, subunit, pdu, + iov_cnt + 1, func, user_data); +} + +static int avrcp_send_browsing_req(struct avrcp *session, uint8_t pdu_id, + const struct iovec *iov, int iov_cnt, + avctp_browsing_rsp_cb func, + void *user_data) +{ + struct iovec pdu[iov_cnt + 1]; + struct avrcp_browsing_header hdr; + int i; + + memset(&hdr, 0, sizeof(hdr)); + + for (i = 0; i < iov_cnt; i++) { + pdu[i + 1].iov_base = iov[i].iov_base; + pdu[i + 1].iov_len = iov[i].iov_len; + hdr.params_len += iov[i].iov_len; + } + + hdr.pdu_id = pdu_id; + hdr.params_len = htons(hdr.params_len); + + pdu[0].iov_base = &hdr; + pdu[0].iov_len = sizeof(hdr); + + return avctp_send_browsing_req(session->conn, pdu, iov_cnt + 1, + func, user_data); +} + +static int avrcp_send_browsing(struct avrcp *session, uint8_t transaction, + uint8_t pdu_id, const struct iovec *iov, + int iov_cnt) +{ + struct iovec pdu[iov_cnt + 1]; + struct avrcp_browsing_header hdr; + int i; + + memset(&hdr, 0, sizeof(hdr)); + + for (i = 0; i < iov_cnt; i++) { + pdu[i + 1].iov_base = iov[i].iov_base; + pdu[i + 1].iov_len = iov[i].iov_len; + hdr.params_len += iov[i].iov_len; + } + + hdr.pdu_id = pdu_id; + hdr.params_len = htons(hdr.params_len); + + pdu[0].iov_base = &hdr; + pdu[0].iov_len = sizeof(hdr); + + return avctp_send_browsing(session->conn, transaction, pdu, + iov_cnt + 1); +} + +static gboolean get_capabilities_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + struct get_capabilities_rsp *rsp; + uint8_t number = 0; + uint8_t *params = NULL; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_capabilities) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + switch (rsp->cap) { + case CAP_COMPANY_ID: + case CAP_EVENTS_SUPPORTED: + break; + default: + err = -EPROTO; + goto done; + } + + if (rsp->number > 0) { + number = rsp->number; + params = rsp->params; + } + + err = 0; + +done: + player->cfm->get_capabilities(session, err, number, params, + player->user_data); + + return FALSE; +} + + +int avrcp_get_capabilities(struct avrcp *session, uint8_t param) +{ + struct iovec iov; + struct get_capabilities_req req; + + req.cap = param; + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_GET_CAPABILITIES, &iov, 1, + get_capabilities_rsp, session); +} + +static gboolean register_notification_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + struct register_notification_rsp *rsp; + uint8_t event = 0; + uint16_t value16, value16_2[2]; + uint32_t value32; + uint64_t value64; + uint8_t *params = NULL; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->register_notification) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + event = rsp->event; + + if (event > AVRCP_EVENT_LAST) { + err = -EPROTO; + goto done; + } + + switch (event) { + case AVRCP_EVENT_STATUS_CHANGED: + if (pdu->params_len != sizeof(*rsp) + sizeof(uint8_t)) { + err = -EPROTO; + goto done; + } + params = rsp->data; + break; + case AVRCP_EVENT_VOLUME_CHANGED: + if (pdu->params_len != sizeof(*rsp) + sizeof(uint8_t)) { + err = -EPROTO; + goto done; + } + params = rsp->data; + params[0] &= 0x7f; + break; + case AVRCP_EVENT_TRACK_CHANGED: + if (pdu->params_len != sizeof(*rsp) + sizeof(value64)) { + err = -EPROTO; + goto done; + } + value64 = get_be64(rsp->data); + params = (uint8_t *) &value64; + break; + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + if (pdu->params_len != sizeof(*rsp) + sizeof(value32)) { + err = -EPROTO; + goto done; + } + value32 = get_be32(rsp->data); + params = (uint8_t *) &value32; + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + if (pdu->params_len < sizeof(*rsp) + sizeof(value16_2)) { + err = -EPROTO; + goto done; + } + value16_2[0] = get_be16(rsp->data); + value16_2[1] = get_be16(rsp->data + 2); + params = (uint8_t *) value16_2; + break; + case AVRCP_EVENT_SETTINGS_CHANGED: + if (pdu->params_len < sizeof(*rsp) + sizeof(uint8_t)) { + err = -EPROTO; + goto done; + } + params = rsp->data; + break; + case AVRCP_EVENT_UIDS_CHANGED: + if (pdu->params_len < sizeof(*rsp) + sizeof(value16)) { + err = -EPROTO; + goto done; + } + value16 = get_be16(rsp->data); + params = (uint8_t *) &value16; + break; + } + + err = 0; + +done: + return player->cfm->register_notification(session, err, code, event, + params, player->user_data); +} + +int avrcp_register_notification(struct avrcp *session, uint8_t event, + uint32_t interval) +{ + struct iovec iov; + struct register_notification_req req; + + if (event > AVRCP_EVENT_LAST) + return -EINVAL; + + req.event = event; + req.interval = cpu_to_be32(interval); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_req(session, AVC_CTYPE_NOTIFY, AVC_SUBUNIT_PANEL, + AVRCP_REGISTER_NOTIFICATION, &iov, 1, + register_notification_rsp, session); +} + +static gboolean list_attributes_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu = (void *) operands; + struct list_attributes_rsp *rsp; + uint8_t number = 0; + uint8_t *attrs = NULL; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->list_attributes) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + rsp = (void *) pdu->params; + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + number = rsp->number; + if (number > 0) + attrs = rsp->params; + + err = 0; + +done: + player->cfm->list_attributes(session, err, number, attrs, + player->user_data); + + return FALSE; +} + +int avrcp_list_player_attributes(struct avrcp *session) +{ + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_LIST_PLAYER_ATTRIBUTES, NULL, 0, + list_attributes_rsp, session); +} + +static int parse_text_rsp(struct avrcp_header *pdu, uint8_t *number, + uint8_t *attrs, char **text) +{ + uint8_t *ptr; + uint16_t params_len; + int i; + + if (pdu->params_len < 1) + return -EPROTO; + + *number = pdu->params[0]; + if (*number > AVRCP_ATTRIBUTE_LAST) { + *number = 0; + return -EPROTO; + } + + params_len = pdu->params_len - 1; + for (i = 0, ptr = &pdu->params[1]; i < *number && params_len > 0; i++) { + struct text_value *val; + + if (params_len < sizeof(*val)) + goto fail; + + val = (void *) ptr; + + attrs[i] = val->attr; + + params_len -= sizeof(*val); + ptr += sizeof(*val); + + if (val->len > params_len) + goto fail; + + if (val->len > 0) { + text[i] = g_strndup(val->data, val->len); + params_len -= val->len; + ptr += val->len; + } + } + + if (i != *number) + goto fail; + + return 0; + +fail: + for (i -= 1; i >= 0; i--) + g_free(text[i]); + + *number = 0; + + return -EPROTO; +} + +static gboolean get_attribute_text_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + uint8_t number = 0; + uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; + char *text[AVRCP_ATTRIBUTE_LAST]; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_attribute_text) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + err = parse_text_rsp(pdu, &number, attrs, text); + +done: + player->cfm->get_attribute_text(session, err, number, attrs, text, + player->user_data); + + return FALSE; +} + +int avrcp_get_player_attribute_text(struct avrcp *session, uint8_t number, + uint8_t *attrs) +{ + struct iovec iov[2]; + + if (!number || number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + iov[1].iov_base = attrs; + iov[1].iov_len = number; + + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, iov, 2, + get_attribute_text_rsp, session); +} + +static gboolean list_values_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + struct list_values_rsp *rsp; + uint8_t number = 0; + uint8_t *values = NULL; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->list_values) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + if (rsp->number > 0) { + number = rsp->number; + values = rsp->params; + } + + err = 0; + +done: + player->cfm->list_values(session, err, number, values, + player->user_data); + + return FALSE; +} + +int avrcp_list_player_values(struct avrcp *session, uint8_t attr) +{ + struct iovec iov; + + iov.iov_base = &attr; + iov.iov_len = sizeof(attr); + + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_LIST_PLAYER_VALUES, &iov, 1, + list_values_rsp, session); +} + +static gboolean get_value_text_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + uint8_t number = 0; + uint8_t values[AVRCP_ATTRIBUTE_LAST]; + char *text[AVRCP_ATTRIBUTE_LAST]; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_value_text) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + err = parse_text_rsp(pdu, &number, values, text); + +done: + player->cfm->get_value_text(session, err, number, values, text, + player->user_data); + + return FALSE; +} + +int avrcp_get_player_value_text(struct avrcp *session, uint8_t attr, + uint8_t number, uint8_t *values) +{ + struct iovec iov[2]; + struct get_value_text_req req; + + if (!number) + return -EINVAL; + + req.attr = attr; + req.number = number; + + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + + iov[1].iov_base = values; + iov[1].iov_len = number; + + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_GET_PLAYER_VALUE_TEXT, iov, 2, + get_value_text_rsp, session); +} + +static int parse_value(struct avrcp_header *pdu, uint8_t *number, + uint8_t *attrs, uint8_t *values) +{ + int i; + struct value_rsp *rsp; + + if (pdu->params_len < sizeof(*rsp)) + return -EPROTO; + + rsp = (void *) pdu->params; + + /* + * Check if PDU is big enough to hold the number of (attribute, value) + * tuples. + */ + if (rsp->number > AVRCP_ATTRIBUTE_LAST || + sizeof(*rsp) + rsp->number * 2 != pdu->params_len) { + *number = 0; + return -EPROTO; + } + + for (i = 0; i < rsp->number; i++) { + attrs[i] = rsp->values[i].attr; + values[i] = rsp->values[i].value; + } + + *number = rsp->number; + + return 0; +} + +static gboolean get_value_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + uint8_t number = 0; + uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; + uint8_t values[AVRCP_ATTRIBUTE_LAST]; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_value) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + err = parse_value(pdu, &number, attrs, values); + +done: + player->cfm->get_value(session, err, number, attrs, values, + player->user_data); + + return FALSE; +} + +int avrcp_get_current_player_value(struct avrcp *session, uint8_t number, + uint8_t *attrs) + +{ + struct iovec iov[2]; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + iov[1].iov_base = attrs; + iov[1].iov_len = number; + + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_GET_CURRENT_PLAYER_VALUE, iov, 2, + get_value_rsp, session); +} + +static gboolean set_value_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->set_value) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + err = 0; + +done: + player->cfm->set_value(session, err, player->user_data); + + return FALSE; +} + +int avrcp_set_player_value(struct avrcp *session, uint8_t number, + uint8_t *attrs, uint8_t *values) +{ + struct iovec iov[2]; + struct attr_value val[AVRCP_ATTRIBUTE_LAST]; + int i; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + for (i = 0; i < number; i++) { + val[i].attr = attrs[i]; + val[i].value = values[i]; + } + + iov[1].iov_base = val; + iov[1].iov_len = sizeof(*val) * number; + + return avrcp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, + AVRCP_SET_PLAYER_VALUE, iov, 2, + set_value_rsp, session); +} + +static gboolean get_play_status_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + struct get_play_status_rsp *rsp; + uint8_t status = 0; + uint32_t position = 0; + uint32_t duration = 0; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_play_status) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + duration = be32_to_cpu(rsp->duration); + position = be32_to_cpu(rsp->position); + status = rsp->status; + err = 0; + +done: + player->cfm->get_play_status(session, err, status, position, duration, + player->user_data); + + return FALSE; +} + +int avrcp_get_play_status(struct avrcp *session) +{ + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_GET_PLAY_STATUS, NULL, 0, + get_play_status_rsp, session); +} + +static gboolean set_volume_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + struct set_volume_rsp *rsp; + uint8_t value = 0; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->set_volume) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + value = rsp->value & 0x7f; + err = 0; + +done: + player->cfm->set_volume(session, err, value, player->user_data); + + return FALSE; +} + +int avrcp_set_volume(struct avrcp *session, uint8_t volume) +{ + struct iovec iov; + + iov.iov_base = &volume; + iov.iov_len = sizeof(volume); + + return avrcp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, + AVRCP_SET_ABSOLUTE_VOLUME, &iov, 1, + set_volume_rsp, session); +} + +static int parse_attribute_list(uint8_t *params, uint16_t params_len, + uint8_t number, uint32_t *attrs, char **text) +{ + struct media_item *item; + int i; + + if (number > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EPROTO; + + for (i = 0; i < number && params_len >= sizeof(*item); i++) { + item = (void *) params; + + item->attr = be32_to_cpu(item->attr); + item->charset = be16_to_cpu(item->charset); + item->len = be16_to_cpu(item->len); + + params_len -= sizeof(*item); + params += sizeof(*item); + if (item->len > params_len) + goto fail; + + if (item->len > 0) { + text[i] = g_strndup(item->data, item->len); + attrs[i] = item->attr; + params_len -= item->len; + params += item->len; + } else { + text[i] = NULL; + attrs[i] = 0; + } + } + + return 0; + +fail: + for (i -= 1; i >= 0; i--) + g_free(text[i]); + + return -EPROTO; +} + +static void free_attribute_list(uint8_t number, char **text) +{ + while(number--) + g_free(text[number]); +} + +static int parse_elements(struct avrcp_header *pdu, uint8_t *number, + uint32_t *attrs, char **text) +{ + struct get_element_attributes_rsp *rsp; + + if (pdu->params_len < sizeof(*rsp)) + return -EPROTO; + + rsp = (void *) pdu->params; + if (rsp->number > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EPROTO; + + *number = rsp->number; + + return parse_attribute_list(pdu->params + sizeof(*rsp), + pdu->params_len - sizeof(*rsp), + *number, attrs, text); +} + +static int parse_items(struct avrcp_browsing_header *pdu, uint8_t *number, + uint32_t *attrs, char **text) +{ + struct get_item_attributes_rsp *rsp; + + if (pdu->params_len < sizeof(*rsp)) + return -EPROTO; + + rsp = (void *) pdu->params; + + if (rsp->number > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EPROTO; + + *number = rsp->number; + + return parse_attribute_list(pdu->params + sizeof(*rsp), + pdu->params_len - sizeof(*rsp), + *number, attrs, text); +} + +static gboolean get_element_attributes_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + uint8_t number = 0; + uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; + char *text[AVRCP_MEDIA_ATTRIBUTE_LAST]; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_element_attributes) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + if (code == AVC_CTYPE_REJECTED) { + err = parse_status(pdu); + goto done; + } + + err = parse_elements(pdu, &number, attrs, text); + +done: + player->cfm->get_element_attributes(session, err, number, attrs, text, + player->user_data); + + if (err == 0) + free_attribute_list(number, text); + + return FALSE; +} + +int avrcp_get_element_attributes(struct avrcp *session) +{ + struct iovec iov; + struct get_element_attributes_req req; + + /* This returns all attributes */ + memset(&req, 0, sizeof(req)); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, + AVRCP_GET_ELEMENT_ATTRIBUTES, &iov, 1, + get_element_attributes_rsp, session); +} + +static gboolean set_addressed_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_header *pdu; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->set_addressed) + return FALSE; + + pdu = parse_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_status(pdu); + +done: + player->cfm->set_addressed(session, err, player->user_data); + + return FALSE; +} + +int avrcp_set_addressed_player(struct avrcp *session, uint16_t player_id) +{ + struct iovec iov; + struct set_addressed_req req; + + req.id = cpu_to_be16(player_id); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, + AVRCP_SET_ADDRESSED_PLAYER, &iov, 1, + set_addressed_rsp, session); +} + +static char *parse_folder_list(uint8_t *params, uint16_t params_len, + uint8_t depth) +{ + char **folders, *path; + uint8_t count; + size_t i; + + folders = g_new0(char *, depth + 2); + folders[0] = g_strdup("/Filesystem"); + + for (i = 0, count = 1; count <= depth && i < params_len; count++) { + uint8_t len; + + len = params[i++]; + + if (i + len > params_len || len == 0) { + g_strfreev(folders); + return NULL; + } + + folders[count] = g_memdup(¶ms[i], len); + i += len; + } + + path = g_build_pathv("/", folders); + g_strfreev(folders); + + return path; +} + +static gboolean set_browsed_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + struct set_browsed_rsp *rsp; + uint16_t counter = 0; + uint32_t items = 0; + char *path = NULL; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->set_browsed) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + if (err < 0) + goto done; + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + counter = be16_to_cpu(rsp->counter); + items = be32_to_cpu(rsp->items); + + path = parse_folder_list(rsp->data, pdu->params_len - sizeof(*rsp), + rsp->depth); + if (!path) + err = -EPROTO; + +done: + player->cfm->set_browsed(session, err, counter, items, path, + player->user_data); + + return FALSE; +} + +int avrcp_set_browsed_player(struct avrcp *session, uint16_t player_id) +{ + struct iovec iov; + struct set_browsed_req req; + + req.id = cpu_to_be16(player_id); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_browsing_req(session, AVRCP_SET_BROWSED_PLAYER, + &iov, 1, set_browsed_rsp, session); +} + +static gboolean get_folder_items_rsp(struct avctp *conn, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + struct get_folder_items_rsp *rsp; + uint16_t counter = 0, number = 0; + uint8_t *params = NULL; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_folder_items) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + if (err < 0) + goto done; + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + counter = be16_to_cpu(rsp->counter); + number = be16_to_cpu(rsp->number); + params = rsp->data; + + /* FIXME: Add proper parsing for each item type */ + +done: + player->cfm->get_folder_items(session, err, counter, number, params, + player->user_data); + + return FALSE; +} + +int avrcp_get_folder_items(struct avrcp *session, uint8_t scope, + uint32_t start, uint32_t end, uint8_t number, + uint32_t *attrs) +{ + + struct iovec iov[2]; + struct get_folder_items_req req; + int i; + + req.scope = scope; + req.start = cpu_to_be32(start); + req.end = cpu_to_be32(end); + req.number = number; + + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + + if (!number) + return avrcp_send_browsing_req(session, AVRCP_GET_FOLDER_ITEMS, + iov, 1, get_folder_items_rsp, + session); + + for (i = 0; i < number; i++) + attrs[i] = cpu_to_be32(attrs[i]); + + iov[1].iov_base = attrs; + iov[1].iov_len = number * sizeof(*attrs); + + return avrcp_send_browsing_req(session, AVRCP_GET_FOLDER_ITEMS, + iov, 2, get_folder_items_rsp, session); +} + +static gboolean change_path_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + struct change_path_rsp *rsp; + uint32_t items = 0; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->change_path) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + if (err < 0) + goto done; + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + items = be32_to_cpu(rsp->items); + +done: + player->cfm->change_path(session, err, items, player->user_data); + + return FALSE; +} + +int avrcp_change_path(struct avrcp *session, uint8_t direction, uint64_t uid, + uint16_t counter) +{ + struct iovec iov; + struct change_path_req req; + + req.counter = cpu_to_be16(counter); + req.direction = direction; + req.uid = cpu_to_be64(uid); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_browsing_req(session, AVRCP_CHANGE_PATH, + &iov, 1, change_path_rsp, session); +} + +static gboolean get_item_attributes_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + uint8_t number = 0; + uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; + char *text[AVRCP_MEDIA_ATTRIBUTE_LAST]; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->get_item_attributes) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + if (err < 0) + goto done; + + err = parse_items(pdu, &number, attrs, text); + +done: + player->cfm->get_item_attributes(session, err, number, attrs, text, + player->user_data); + + if (err == 0) + free_attribute_list(number, text); + + return FALSE; +} + +int avrcp_get_item_attributes(struct avrcp *session, uint8_t scope, + uint64_t uid, uint16_t counter, uint8_t number, + uint32_t *attrs) +{ + struct iovec iov[2]; + struct get_item_attributes_req req; + int i; + + req.scope = scope; + req.uid = cpu_to_be64(uid); + req.counter = cpu_to_be16(counter); + req.number = number; + + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + + if (!number) + return avrcp_send_browsing_req(session, + AVRCP_GET_ITEM_ATTRIBUTES, + iov, 1, get_item_attributes_rsp, + session); + + if (number > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EINVAL; + + for (i = 0; i < number; i++) { + if (attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST || + attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL) + return -EINVAL; + attrs[i] = cpu_to_be32(attrs[i]); + } + + iov[1].iov_base = attrs; + iov[1].iov_len = number * sizeof(*attrs); + + return avrcp_send_browsing_req(session, AVRCP_GET_ITEM_ATTRIBUTES, + iov, 2, get_item_attributes_rsp, + session); +} + +static gboolean play_item_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->play_item) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + +done: + player->cfm->play_item(session, err, player->user_data); + + return FALSE; +} + +int avrcp_play_item(struct avrcp *session, uint8_t scope, uint64_t uid, + uint16_t counter) +{ + struct iovec iov; + struct play_item_req req; + + if (scope > AVRCP_MEDIA_NOW_PLAYING) + return -EINVAL; + + req.scope = scope; + req.uid = cpu_to_be64(uid); + req.counter = cpu_to_be16(counter); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_browsing_req(session, AVRCP_PLAY_ITEM, &iov, 1, + play_item_rsp, session); +} + +static gboolean search_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + struct search_rsp *rsp; + uint16_t counter = 0; + uint32_t items = 0; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->search) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + if (err < 0) + goto done; + + if (pdu->params_len < sizeof(*rsp)) { + err = -EPROTO; + goto done; + } + + rsp = (void *) pdu->params; + + counter = be16_to_cpu(rsp->counter); + items = be32_to_cpu(rsp->items); + + err = 0; + +done: + player->cfm->search(session, err, counter, items, player->user_data); + + return FALSE; +} + +int avrcp_search(struct avrcp *session, const char *string) +{ + struct iovec iov[2]; + struct search_req req; + size_t len; + + if (!string) + return -EINVAL; + + len = strnlen(string, UINT8_MAX); + + req.charset = cpu_to_be16(AVRCP_CHARSET_UTF8); + req.len = cpu_to_be16(len); + + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + + iov[1].iov_base = (void *) string; + iov[1].iov_len = len; + + return avrcp_send_browsing_req(session, AVRCP_SEARCH, iov, 2, + search_rsp, session); +} + +static gboolean add_to_now_playing_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; + struct avrcp_browsing_header *pdu; + int err; + + DBG(""); + + if (!player || !player->cfm || !player->cfm->add_to_now_playing) + return FALSE; + + pdu = parse_browsing_pdu(operands, operand_count); + if (!pdu) { + err = -EPROTO; + goto done; + } + + err = parse_browsing_status(pdu); + +done: + player->cfm->add_to_now_playing(session, err, player->user_data); + + return FALSE; +} + +int avrcp_add_to_now_playing(struct avrcp *session, uint8_t scope, uint64_t uid, + uint16_t counter) +{ + struct iovec iov; + struct add_to_now_playing_req req; + + if (scope > AVRCP_MEDIA_NOW_PLAYING) + return -EINVAL; + + req.scope = scope; + req.uid = cpu_to_be64(uid); + req.counter = cpu_to_be16(counter); + + iov.iov_base = &req; + iov.iov_len = sizeof(req); + + return avrcp_send_browsing_req(session, AVRCP_ADD_TO_NOW_PLAYING, + &iov, 1, add_to_now_playing_rsp, + session); +} + +int avrcp_get_capabilities_rsp(struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *events) +{ + struct iovec iov[2]; + struct get_capabilities_rsp rsp; + + if (number > AVRCP_EVENT_LAST) + return -EINVAL; + + rsp.cap = CAP_EVENTS_SUPPORTED; + rsp.number = number; + + iov[0].iov_base = &rsp; + iov[0].iov_len = sizeof(rsp); + + iov[1].iov_base = events; + iov[1].iov_len = number; + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_GET_CAPABILITIES, + iov, 2); +} + +int avrcp_list_player_attributes_rsp(struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *attrs) +{ + struct iovec iov[2]; + struct list_attributes_rsp rsp; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + rsp.number = number; + + iov[0].iov_base = &rsp; + iov[0].iov_len = sizeof(rsp); + + if (!number) + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_ATTRIBUTES, + iov, 1); + + iov[1].iov_base = attrs; + iov[1].iov_len = number; + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_ATTRIBUTES, + iov, 2); +} + +int avrcp_get_player_attribute_text_rsp(struct avrcp *session, + uint8_t transaction, uint8_t number, + uint8_t *attrs, const char **text) +{ + struct iovec iov[1 + AVRCP_ATTRIBUTE_LAST * 2]; + struct text_value val[AVRCP_ATTRIBUTE_LAST]; + int i; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + for (i = 0; i < number; i++) { + uint8_t len = 0; + + if (attrs[i] > AVRCP_ATTRIBUTE_LAST || + attrs[i] == AVRCP_ATTRIBUTE_ILEGAL) + return -EINVAL; + + if (text[i]) + len = strlen(text[i]); + + val[i].attr = attrs[i]; + val[i].charset = cpu_to_be16(AVRCP_CHARSET_UTF8); + val[i].len = len; + + iov[i + 1].iov_base = &val[i]; + iov[i + 1].iov_len = sizeof(val[i]); + + iov[i + 2].iov_base = (void *) text[i]; + iov[i + 2].iov_len = len; + } + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, + iov, 1 + i * 2); +} + +int avrcp_list_player_values_rsp(struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *values) +{ + struct iovec iov[2]; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + iov[1].iov_base = values; + iov[1].iov_len = number; + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_VALUES, + iov, 2); +} + +int avrcp_get_play_status_rsp(struct avrcp *session, uint8_t transaction, + uint32_t position, uint32_t duration, + uint8_t status) +{ + struct iovec iov; + struct get_play_status_rsp rsp; + + rsp.duration = cpu_to_be32(duration); + rsp.position = cpu_to_be32(position); + rsp.status = status; + + iov.iov_base = &rsp; + iov.iov_len = sizeof(rsp); + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_GET_PLAY_STATUS, + &iov, 1); +} + +int avrcp_get_player_values_text_rsp(struct avrcp *session, + uint8_t transaction, uint8_t number, + uint8_t *values, const char **text) +{ + struct iovec iov[1 + AVRCP_ATTRIBUTE_LAST * 2]; + struct text_value val[AVRCP_ATTRIBUTE_LAST]; + int i; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + for (i = 0; i < number; i++) { + uint8_t len = 0; + + if (text[i]) + len = strlen(text[i]); + + val[i].attr = values[i]; + val[i].charset = cpu_to_be16(AVRCP_CHARSET_UTF8); + val[i].len = len; + + iov[i + 1].iov_base = &val[i]; + iov[i + 1].iov_len = sizeof(val[i]); + + iov[i + 2].iov_base = (void *) text[i]; + iov[i + 2].iov_len = len; + } + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_GET_PLAYER_VALUE_TEXT, + iov, 1 + i * 2); +} + +int avrcp_get_current_player_value_rsp(struct avrcp *session, + uint8_t transaction, uint8_t number, + uint8_t *attrs, uint8_t *values) +{ + struct iovec iov[1 + AVRCP_ATTRIBUTE_LAST]; + struct attr_value val[AVRCP_ATTRIBUTE_LAST]; + int i; + + if (number > AVRCP_ATTRIBUTE_LAST) + return -EINVAL; + + iov[0].iov_base = &number; + iov[0].iov_len = sizeof(number); + + for (i = 0; i < number; i++) { + val[i].attr = attrs[i]; + val[i].value = values[i]; + + iov[i + 1].iov_base = &val[i]; + iov[i + 1].iov_len = sizeof(val[i]); + } + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_GET_CURRENT_PLAYER_VALUE, + iov, 1 + i); +} + +int avrcp_set_player_value_rsp(struct avrcp *session, uint8_t transaction) +{ + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_SET_PLAYER_VALUE, NULL, 0); +} + +int avrcp_get_element_attrs_rsp(struct avrcp *session, uint8_t transaction, + uint8_t *params, size_t params_len) +{ + struct iovec iov; + + iov.iov_base = params; + iov.iov_len = params_len; + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_GET_ELEMENT_ATTRIBUTES, + &iov, 1); +} + +int avrcp_register_notification_rsp(struct avrcp *session, uint8_t transaction, + uint8_t code, uint8_t event, + void *data, size_t len) +{ + struct iovec iov[2]; + uint16_t *player; + uint8_t *volume; + + if (event > AVRCP_EVENT_LAST) + return -EINVAL; + + iov[0].iov_base = &event; + iov[0].iov_len = sizeof(event); + + switch (event) { + case AVRCP_EVENT_STATUS_CHANGED: + if (len != sizeof(uint8_t)) + return -EINVAL; + break; + case AVRCP_EVENT_VOLUME_CHANGED: + if (len != sizeof(uint8_t)) + return -EINVAL; + volume = data; + if (volume[0] > 127) + return -EINVAL; + break; + case AVRCP_EVENT_TRACK_CHANGED: + if (len != sizeof(uint64_t)) + return -EINVAL; + + put_be64(*(uint64_t *) data, data); + break; + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + if (len != sizeof(uint32_t)) + return -EINVAL; + + put_be32(*(uint32_t *) data, data); + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + if (len != 4) + return -EINVAL; + + player = data; + player[0] = cpu_to_be16(player[0]); + player[1] = cpu_to_be16(player[1]); + + break; + case AVRCP_EVENT_SETTINGS_CHANGED: + if (len < sizeof(uint8_t)) + return -EINVAL; + break; + case AVRCP_EVENT_UIDS_CHANGED: + if (len != sizeof(uint16_t)) + return -EINVAL; + + put_be16(*(uint16_t *) data, data); + break; + default: + return avrcp_send(session, transaction, code, AVC_SUBUNIT_PANEL, + AVRCP_REGISTER_NOTIFICATION, iov, 1); + } + + iov[1].iov_base = data; + iov[1].iov_len = len; + + return avrcp_send(session, transaction, code, AVC_SUBUNIT_PANEL, + AVRCP_REGISTER_NOTIFICATION, iov, 2); +} + +int avrcp_set_volume_rsp(struct avrcp *session, uint8_t transaction, + uint8_t volume) +{ + struct iovec iov; + + if (volume > 127) + return -EINVAL; + + iov.iov_base = &volume; + iov.iov_len = sizeof(volume); + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_SET_ABSOLUTE_VOLUME, + &iov, 1); +} + +int avrcp_set_addressed_player_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status) +{ + struct iovec iov; + + iov.iov_base = &status; + iov.iov_len = sizeof(status); + + return avrcp_send(session, transaction, AVC_CTYPE_STABLE, + AVC_SUBUNIT_PANEL, AVRCP_SET_ADDRESSED_PLAYER, + &iov, 1); +} + +static int avrcp_status_rsp(struct avrcp *session, uint8_t transaction, + uint8_t pdu_id, uint8_t status) +{ + struct iovec iov; + + if (status > AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) + return -EINVAL; + + iov.iov_base = &status; + iov.iov_len = sizeof(status); + + return avrcp_send_browsing(session, transaction, pdu_id, &iov, 1); +} + +int avrcp_set_browsed_player_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint16_t counter, + uint32_t items, uint8_t depth, + const char **folders) +{ + struct iovec iov[UINT8_MAX * 2 + 1]; + struct set_browsed_rsp rsp; + uint16_t len[UINT8_MAX]; + int i; + + if (status != AVRCP_STATUS_SUCCESS) + return avrcp_status_rsp(session, transaction, + AVRCP_SET_BROWSED_PLAYER, status); + + rsp.status = status; + rsp.counter = cpu_to_be16(counter); + rsp.items = cpu_to_be32(items); + rsp.charset = cpu_to_be16(AVRCP_CHARSET_UTF8); + rsp.depth = depth; + + iov[0].iov_base = &rsp; + iov[0].iov_len = sizeof(rsp); + + if (!depth) + return avrcp_send_browsing(session, transaction, + AVRCP_SET_BROWSED_PLAYER, + iov, 1); + + for (i = 0; i < depth; i++) { + if (!folders[i]) + return -EINVAL; + + len[i] = strlen(folders[i]); + + iov[i * 2 + 2].iov_base = (void *) folders[i]; + iov[i * 2 + 2].iov_len = len[i]; + + len[i] = cpu_to_be16(len[i]); + + iov[i * 2 + 1].iov_base = &len[i]; + iov[i * 2 + 1].iov_len = sizeof(len[i]); + } + + return avrcp_send_browsing(session, transaction, + AVRCP_SET_BROWSED_PLAYER, iov, + depth * 2 + 1); +} + +int avrcp_get_folder_items_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint16_t counter, + uint8_t number, uint8_t *type, + uint16_t *len, uint8_t **params) +{ + struct iovec iov[UINT8_MAX * 2 + 1]; + struct get_folder_items_rsp rsp; + uint8_t item[UINT8_MAX][3]; + int i; + + if (status != AVRCP_STATUS_SUCCESS) + return avrcp_status_rsp(session, transaction, + AVRCP_GET_FOLDER_ITEMS, status); + + rsp.status = status; + rsp.counter = cpu_to_be16(counter); + rsp.number = cpu_to_be16(number); + + iov[0].iov_base = &rsp; + iov[0].iov_len = sizeof(rsp); + + for (i = 0; i < number; i++) { + item[i][0] = type[i]; + put_be16(len[i], &item[i][1]); + + iov[i * 2 + 1].iov_base = item[i]; + iov[i * 2 + 1].iov_len = sizeof(item[i]); + + iov[i * 2 + 2].iov_base = params[i]; + iov[i * 2 + 2].iov_len = len[i]; + } + + return avrcp_send_browsing(session, transaction, AVRCP_GET_FOLDER_ITEMS, + iov, number * 2 + 1); +} + +int avrcp_change_path_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint32_t items) +{ + struct iovec iov; + struct change_path_rsp rsp; + + if (status != AVRCP_STATUS_SUCCESS) + return avrcp_status_rsp(session, transaction, AVRCP_CHANGE_PATH, + status); + + rsp.status = status; + rsp.items = cpu_to_be32(items); + + iov.iov_base = &rsp; + iov.iov_len = sizeof(rsp); + + return avrcp_send_browsing(session, transaction, AVRCP_CHANGE_PATH, + &iov, 1); +} + +static bool pack_attribute_list(struct iovec *iov, uint8_t number, + uint32_t *attrs, const char **text) +{ + int i; + struct media_item val[AVRCP_MEDIA_ATTRIBUTE_LAST]; + + for (i = 0; i < number; i++) { + uint16_t len = 0; + + if (attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST || + attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL) + return false; + + if (text[i]) + len = strlen(text[i]); + + val[i].attr = cpu_to_be32(attrs[i]); + val[i].charset = cpu_to_be16(AVRCP_CHARSET_UTF8); + val[i].len = cpu_to_be16(len); + + iov[i].iov_base = &val[i]; + iov[i].iov_len = sizeof(val[i]); + + iov[i + 1].iov_base = (void *) text[i]; + iov[i + 1].iov_len = len; + } + + return true; +} + +int avrcp_get_item_attributes_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint8_t number, + uint32_t *attrs, const char **text) +{ + struct iovec iov[AVRCP_MEDIA_ATTRIBUTE_LAST * 2 + 1]; + struct get_item_attributes_rsp rsp; + + if (number > AVRCP_MEDIA_ATTRIBUTE_LAST) + return -EINVAL; + + if (status != AVRCP_STATUS_SUCCESS) + return avrcp_status_rsp(session, transaction, + AVRCP_GET_ITEM_ATTRIBUTES, status); + + rsp.status = status; + rsp.number = number; + + iov[0].iov_base = &rsp; + iov[0].iov_len = sizeof(rsp); + + if (!pack_attribute_list(&iov[1], number, attrs, text)) + return -EINVAL; + + return avrcp_send_browsing(session, transaction, + AVRCP_GET_ITEM_ATTRIBUTES, iov, + number * 2 + 1); +} + +int avrcp_play_item_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status) +{ + return avrcp_status_rsp(session, transaction, AVRCP_PLAY_ITEM, + status); +} + +int avrcp_search_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, + uint16_t counter, uint32_t items) +{ + struct iovec iov; + struct search_rsp rsp; + + if (status != AVRCP_STATUS_SUCCESS) + return avrcp_status_rsp(session, transaction, AVRCP_SEARCH, + status); + + rsp.status = status; + rsp.counter = cpu_to_be16(counter); + rsp.items = cpu_to_be32(items); + + iov.iov_base = &rsp; + iov.iov_len = sizeof(rsp); + + return avrcp_send_browsing(session, transaction, AVRCP_SEARCH, + &iov, 1); +} + +int avrcp_add_to_now_playing_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status) +{ + return avrcp_status_rsp(session, transaction, AVRCP_ADD_TO_NOW_PLAYING, + status); +} + +int avrcp_send_passthrough(struct avrcp *session, uint32_t vendor, uint8_t op) +{ + uint8_t params[5]; + + if (!vendor) + return avctp_send_passthrough(session->conn, op, NULL, 0); + + hton24(params, vendor); + put_be16(op, ¶ms[3]); + + return avctp_send_passthrough(session->conn, AVC_VENDOR_UNIQUE, params, + sizeof(params)); +}
diff --git a/android/avrcp-lib.h b/android/avrcp-lib.h new file mode 100644 index 0000000..6554b12 --- /dev/null +++ b/android/avrcp-lib.h
@@ -0,0 +1,356 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +/* Control PDU ids */ +#define AVRCP_GET_CAPABILITIES 0x10 +#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11 +#define AVRCP_LIST_PLAYER_VALUES 0x12 +#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13 +#define AVRCP_SET_PLAYER_VALUE 0x14 +#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15 +#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16 +#define AVRCP_DISPLAYABLE_CHARSET 0x17 +#define AVRCP_CT_BATTERY_STATUS 0x18 +#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 +#define AVRCP_GET_PLAY_STATUS 0x30 +#define AVRCP_REGISTER_NOTIFICATION 0x31 +#define AVRCP_REQUEST_CONTINUING 0x40 +#define AVRCP_ABORT_CONTINUING 0x41 +#define AVRCP_SET_ABSOLUTE_VOLUME 0x50 +#define AVRCP_SET_ADDRESSED_PLAYER 0x60 +#define AVRCP_SET_BROWSED_PLAYER 0x70 +#define AVRCP_GET_FOLDER_ITEMS 0x71 +#define AVRCP_CHANGE_PATH 0x72 +#define AVRCP_GET_ITEM_ATTRIBUTES 0x73 +#define AVRCP_PLAY_ITEM 0x74 +#define AVRCP_SEARCH 0x80 +#define AVRCP_ADD_TO_NOW_PLAYING 0x90 +#define AVRCP_GENERAL_REJECT 0xA0 + +/* Notification events */ +#define AVRCP_EVENT_STATUS_CHANGED 0x01 +#define AVRCP_EVENT_TRACK_CHANGED 0x02 +#define AVRCP_EVENT_TRACK_REACHED_END 0x03 +#define AVRCP_EVENT_TRACK_REACHED_START 0x04 +#define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05 +#define AVRCP_EVENT_SETTINGS_CHANGED 0x08 +#define AVRCP_EVENT_NOW_PLAYING_CONTENT_CHANGED 0x09 +#define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a +#define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b +#define AVRCP_EVENT_UIDS_CHANGED 0x0c +#define AVRCP_EVENT_VOLUME_CHANGED 0x0d +#define AVRCP_EVENT_LAST AVRCP_EVENT_VOLUME_CHANGED + +/* Status codes */ +#define AVRCP_STATUS_INVALID_COMMAND 0x00 +#define AVRCP_STATUS_INVALID_PARAM 0x01 +#define AVRCP_STATUS_PARAM_NOT_FOUND 0x02 +#define AVRCP_STATUS_INTERNAL_ERROR 0x03 +#define AVRCP_STATUS_SUCCESS 0x04 +#define AVRCP_STATUS_UID_CHANGED 0x05 +#define AVRCP_STATUS_NOT_DIRECTORY 0x08 +#define AVRCP_STATUS_DOES_NOT_EXIST 0x09 +#define AVRCP_STATUS_INVALID_SCOPE 0x0a +#define AVRCP_STATUS_OUT_OF_BOUNDS 0x0b +#define AVRCP_STATUS_INVALID_PLAYER_ID 0x11 +#define AVRCP_STATUS_PLAYER_NOT_BROWSABLE 0x12 +#define AVRCP_STATUS_NO_AVAILABLE_PLAYERS 0x15 +#define AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED 0x16 + +/* Capabilities for AVRCP_GET_CAPABILITIES pdu */ +#define CAP_COMPANY_ID 0x02 +#define CAP_EVENTS_SUPPORTED 0x03 + +/* Player Attributes */ +#define AVRCP_ATTRIBUTE_ILEGAL 0x00 +#define AVRCP_ATTRIBUTE_EQUALIZER 0x01 +#define AVRCP_ATTRIBUTE_REPEAT_MODE 0x02 +#define AVRCP_ATTRIBUTE_SHUFFLE 0x03 +#define AVRCP_ATTRIBUTE_SCAN 0x04 +#define AVRCP_ATTRIBUTE_LAST AVRCP_ATTRIBUTE_SCAN + +/* equalizer values */ +#define AVRCP_EQUALIZER_OFF 0x01 +#define AVRCP_EQUALIZER_ON 0x02 + +/* repeat mode values */ +#define AVRCP_REPEAT_MODE_OFF 0x01 +#define AVRCP_REPEAT_MODE_SINGLE 0x02 +#define AVRCP_REPEAT_MODE_ALL 0x03 +#define AVRCP_REPEAT_MODE_GROUP 0x04 + +/* shuffle values */ +#define AVRCP_SHUFFLE_OFF 0x01 +#define AVRCP_SHUFFLE_ALL 0x02 +#define AVRCP_SHUFFLE_GROUP 0x03 + +/* scan values */ +#define AVRCP_SCAN_OFF 0x01 +#define AVRCP_SCAN_ALL 0x02 +#define AVRCP_SCAN_GROUP 0x03 + +/* media attributes */ +#define AVRCP_MEDIA_ATTRIBUTE_ILLEGAL 0x00 +#define AVRCP_MEDIA_ATTRIBUTE_TITLE 0x01 +#define AVRCP_MEDIA_ATTRIBUTE_ARTIST 0x02 +#define AVRCP_MEDIA_ATTRIBUTE_ALBUM 0x03 +#define AVRCP_MEDIA_ATTRIBUTE_TRACK 0x04 +#define AVRCP_MEDIA_ATTRIBUTE_N_TRACKS 0x05 +#define AVRCP_MEDIA_ATTRIBUTE_GENRE 0x06 +#define AVRCP_MEDIA_ATTRIBUTE_DURATION 0x07 +#define AVRCP_MEDIA_ATTRIBUTE_LAST AVRCP_MEDIA_ATTRIBUTE_DURATION + +/* Media Scope */ +#define AVRCP_MEDIA_PLAYER_LIST 0x00 +#define AVRCP_MEDIA_PLAYER_VFS 0x01 +#define AVRCP_MEDIA_SEARCH 0x02 +#define AVRCP_MEDIA_NOW_PLAYING 0x03 + +/* SDP features */ +#define AVRCP_FEATURE_CATEGORY_1 0x0001 +#define AVRCP_FEATURE_CATEGORY_2 0x0002 +#define AVRCP_FEATURE_CATEGORY_3 0x0004 +#define AVRCP_FEATURE_CATEGORY_4 0x0008 +#define AVRCP_FEATURE_PLAYER_SETTINGS 0x0010 +#define AVRCP_FEATURE_GROUP_NAVIGATION 0x0020 +#define AVRCP_FEATURE_BROWSING 0x0040 +#define AVRCP_FEATURE_MULTIPLE_PLAYERS 0x0080 + +/* Company IDs for vendor dependent commands */ +#define IEEEID_BTSIG 0x001958 + +struct avrcp; + +struct avrcp_control_ind { + int (*get_capabilities) (struct avrcp *session, uint8_t transaction, + void *user_data); + int (*list_attributes) (struct avrcp *session, uint8_t transaction, + void *user_data); + int (*get_attribute_text) (struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *attrs, + void *user_data); + int (*list_values) (struct avrcp *session, uint8_t transaction, + uint8_t attr, void *user_data); + int (*get_value_text) (struct avrcp *session, uint8_t transaction, + uint8_t attr, uint8_t number, + uint8_t *values, void *user_data); + int (*get_value) (struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *attrs, + void *user_data); + int (*set_value) (struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *attrs, + uint8_t *values, void *user_data); + int (*get_play_status) (struct avrcp *session, uint8_t transaction, + void *user_data); + int (*get_element_attributes) (struct avrcp *session, + uint8_t transaction, uint64_t uid, + uint8_t number, uint32_t *attrs, + void *user_data); + int (*register_notification) (struct avrcp *session, + uint8_t transaction, uint8_t event, + uint32_t interval, void *user_data); + int (*set_volume) (struct avrcp *session, uint8_t transaction, + uint8_t volume, void *user_data); + int (*set_addressed) (struct avrcp *session, uint8_t transaction, + uint16_t id, void *user_data); + int (*set_browsed) (struct avrcp *session, uint8_t transaction, + uint16_t id, void *user_data); + int (*get_folder_items) (struct avrcp *session, uint8_t transaction, + uint8_t scope, uint32_t start, + uint32_t end, uint16_t number, + uint32_t *attrs, void *user_data); + int (*change_path) (struct avrcp *session, uint8_t transaction, + uint16_t counter, uint8_t direction, + uint64_t uid, void *user_data); + int (*get_item_attributes) (struct avrcp *session, uint8_t transaction, + uint8_t scope, uint64_t uid, + uint16_t counter, uint8_t number, + uint32_t *attrs, void *user_data); + int (*play_item) (struct avrcp *session, uint8_t transaction, + uint8_t scope, uint64_t uid, + uint16_t counter, void *user_data); + int (*search) (struct avrcp *session, uint8_t transaction, + const char *string, void *user_data); + int (*add_to_now_playing) (struct avrcp *session, uint8_t transaction, + uint8_t scope, uint64_t uid, + uint16_t counter, void *user_data); +}; + +struct avrcp_control_cfm { + void (*get_capabilities) (struct avrcp *session, int err, + uint8_t number, uint8_t *params, + void *user_data); + void (*list_attributes) (struct avrcp *session, int err, + uint8_t number, uint8_t *attrs, + void *user_data); + void (*get_attribute_text) (struct avrcp *session, int err, + uint8_t number, uint8_t *attrs, + char **text, void *user_data); + void (*list_values) (struct avrcp *session, int err, + uint8_t number, uint8_t *values, + void *user_data); + void (*get_value_text) (struct avrcp *session, int err, + uint8_t number, uint8_t *values, + char **text, void *user_data); + void (*get_value) (struct avrcp *session, int err, + uint8_t number, uint8_t *attrs, + uint8_t *values, void *user_data); + void (*set_value) (struct avrcp *session, int err, void *user_data); + void (*get_play_status) (struct avrcp *session, int err, + uint8_t status, uint32_t position, + uint32_t duration, void *user_data); + void (*get_element_attributes) (struct avrcp *session, int err, + uint8_t number, uint32_t *attrs, + char **text, void *user_data); + bool (*register_notification) (struct avrcp *session, int err, + uint8_t code, uint8_t event, + void *params, void *user_data); + void (*set_volume) (struct avrcp *session, int err, uint8_t volume, + void *user_data); + void (*set_addressed) (struct avrcp *session, int err, + void *user_data); + void (*set_browsed) (struct avrcp *session, int err, + uint16_t counter, uint32_t items, + char *path, void *user_data); + void (*get_folder_items) (struct avrcp *session, int err, + uint16_t counter, uint16_t number, + uint8_t *params, void *user_data); + void (*change_path) (struct avrcp *session, int err, + uint32_t items, void *user_data); + void (*get_item_attributes) (struct avrcp *session, int err, + uint8_t number, uint32_t *attrs, + char **text, void *user_data); + void (*play_item) (struct avrcp *session, int err, void *user_data); + void (*search) (struct avrcp *session, int err, uint16_t counter, + uint32_t items, void *user_data); + void (*add_to_now_playing) (struct avrcp *session, int err, + void *user_data); +}; + +struct avrcp_passthrough_handler { + uint8_t op; + bool (*func) (struct avrcp *session, bool pressed, void *user_data); +}; + +typedef void (*avrcp_destroy_cb_t) (void *user_data); + +struct avrcp *avrcp_new(int fd, size_t imtu, size_t omtu, uint16_t version); +void avrcp_shutdown(struct avrcp *session); +void avrcp_set_destroy_cb(struct avrcp *session, avrcp_destroy_cb_t cb, + void *user_data); +int avrcp_connect_browsing(struct avrcp *session, int fd, size_t imtu, + size_t omtu); + +void avrcp_register_player(struct avrcp *session, + const struct avrcp_control_ind *ind, + const struct avrcp_control_cfm *cfm, + void *user_data); +void avrcp_set_passthrough_handlers(struct avrcp *session, + const struct avrcp_passthrough_handler *handlers, + void *user_data); +int avrcp_init_uinput(struct avrcp *session, const char *name, + const char *address); +int avrcp_send(struct avrcp *session, uint8_t transaction, uint8_t code, + uint8_t subunit, uint8_t pdu_id, + const struct iovec *iov, int iov_cnt); +int avrcp_get_capabilities(struct avrcp *session, uint8_t param); +int avrcp_register_notification(struct avrcp *session, uint8_t event, + uint32_t interval); +int avrcp_list_player_attributes(struct avrcp *session); +int avrcp_get_player_attribute_text(struct avrcp *session, uint8_t number, + uint8_t *attrs); +int avrcp_list_player_values(struct avrcp *session, uint8_t attr); +int avrcp_get_player_value_text(struct avrcp *session, uint8_t attr, + uint8_t number, uint8_t *values); +int avrcp_set_player_value(struct avrcp *session, uint8_t number, + uint8_t *attrs, uint8_t *values); +int avrcp_get_current_player_value(struct avrcp *session, uint8_t number, + uint8_t *attrs); +int avrcp_get_play_status(struct avrcp *session); +int avrcp_set_volume(struct avrcp *session, uint8_t volume); +int avrcp_get_element_attributes(struct avrcp *session); +int avrcp_set_addressed_player(struct avrcp *session, uint16_t player_id); +int avrcp_set_browsed_player(struct avrcp *session, uint16_t player_id); +int avrcp_get_folder_items(struct avrcp *session, uint8_t scope, + uint32_t start, uint32_t end, uint8_t number, + uint32_t *attrs); +int avrcp_change_path(struct avrcp *session, uint8_t direction, uint64_t uid, + uint16_t counter); +int avrcp_get_item_attributes(struct avrcp *session, uint8_t scope, + uint64_t uid, uint16_t counter, uint8_t number, + uint32_t *attrs); +int avrcp_play_item(struct avrcp *session, uint8_t scope, uint64_t uid, + uint16_t counter); +int avrcp_search(struct avrcp *session, const char *string); +int avrcp_add_to_now_playing(struct avrcp *session, uint8_t scope, uint64_t uid, + uint16_t counter); + +int avrcp_get_capabilities_rsp(struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *events); +int avrcp_list_player_attributes_rsp(struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *attrs); +int avrcp_get_player_attribute_text_rsp(struct avrcp *session, + uint8_t transaction, uint8_t number, + uint8_t *attrs, const char **text); +int avrcp_list_player_values_rsp(struct avrcp *session, uint8_t transaction, + uint8_t number, uint8_t *values); +int avrcp_get_play_status_rsp(struct avrcp *session, uint8_t transaction, + uint32_t position, uint32_t duration, + uint8_t status); +int avrcp_get_player_values_text_rsp(struct avrcp *session, + uint8_t transaction, uint8_t number, + uint8_t *values, const char **text); +int avrcp_get_current_player_value_rsp(struct avrcp *session, + uint8_t transaction, uint8_t number, + uint8_t *attrs, uint8_t *values); +int avrcp_set_player_value_rsp(struct avrcp *session, uint8_t transaction); +int avrcp_get_element_attrs_rsp(struct avrcp *session, uint8_t transaction, + uint8_t *params, size_t params_len); +int avrcp_register_notification_rsp(struct avrcp *session, uint8_t transaction, + uint8_t code, uint8_t event, + void *data, size_t len); +int avrcp_set_volume_rsp(struct avrcp *session, uint8_t transaction, + uint8_t volume); +int avrcp_set_addressed_player_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status); +int avrcp_set_browsed_player_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint16_t counter, + uint32_t items, uint8_t depth, + const char **folders); +int avrcp_get_folder_items_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint16_t counter, + uint8_t number, uint8_t *type, + uint16_t *len, uint8_t **params); +int avrcp_change_path_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint32_t items); +int avrcp_get_item_attributes_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status, uint8_t number, + uint32_t *attrs, const char **text); +int avrcp_play_item_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status); +int avrcp_search_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, + uint16_t counter, uint32_t items); +int avrcp_add_to_now_playing_rsp(struct avrcp *session, uint8_t transaction, + uint8_t status); + +int avrcp_send_passthrough(struct avrcp *session, uint32_t vendor, uint8_t op);
diff --git a/android/avrcp.c b/android/avrcp.c new file mode 100644 index 0000000..8c3e25a --- /dev/null +++ b/android/avrcp.c
@@ -0,0 +1,1169 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <errno.h> +#include <glib.h> + +#include "btio/btio.h" +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "src/sdp-client.h" +#include "src/shared/util.h" +#include "src/log.h" + +#include "avctp.h" +#include "avrcp-lib.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "bluetooth.h" +#include "avrcp.h" +#include "utils.h" + +#define L2CAP_PSM_AVCTP 0x17 + +static bdaddr_t adapter_addr; +static uint32_t record_tg_id = 0; +static uint32_t record_ct_id = 0; +static GSList *devices = NULL; +static GIOChannel *server = NULL; +static struct ipc *hal_ipc = NULL; + +struct avrcp_request { + struct avrcp_device *dev; + uint8_t pdu_id; + uint8_t event_id; + uint8_t transaction; +}; + +struct avrcp_device { + bdaddr_t dst; + uint16_t version; + uint16_t features; + struct avrcp *session; + GIOChannel *io; + GQueue *queue; +}; + +static struct avrcp_request *pop_request(uint8_t pdu_id, uint8_t event_id, + bool peek) +{ + GSList *l; + + for (l = devices; l; l = g_slist_next(l)) { + struct avrcp_device *dev = l->data; + GList *reqs = g_queue_peek_head_link(dev->queue); + int i; + + for (i = 0; reqs; reqs = g_list_next(reqs), i++) { + struct avrcp_request *req = reqs->data; + + if (req->pdu_id != pdu_id || req->event_id != event_id) + continue; + + if (!peek) + g_queue_pop_nth(dev->queue, i); + + return req; + } + } + + return NULL; +} + +static void handle_get_play_status(const void *buf, uint16_t len) +{ + const struct hal_cmd_avrcp_get_play_status *cmd = buf; + uint8_t status; + struct avrcp_request *req; + int ret; + + DBG(""); + + req = pop_request(AVRCP_GET_PLAY_STATUS, 0, false); + if (!req) { + status = HAL_STATUS_FAILED; + goto done; + } + + ret = avrcp_get_play_status_rsp(req->dev->session, req->transaction, + cmd->position, cmd->duration, + cmd->status); + if (ret < 0) { + status = HAL_STATUS_FAILED; + g_free(req); + goto done; + } + + status = HAL_STATUS_SUCCESS; + g_free(req); + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAY_STATUS, status); +} + +static void handle_list_player_attrs(const void *buf, uint16_t len) +{ + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_LIST_PLAYER_ATTRS, HAL_STATUS_FAILED); +} + +static void handle_list_player_values(const void *buf, uint16_t len) +{ + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_LIST_PLAYER_VALUES, HAL_STATUS_FAILED); +} + +static void handle_get_player_attrs(const void *buf, uint16_t len) +{ + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAYER_ATTRS, HAL_STATUS_FAILED); +} + +static void handle_get_player_attrs_text(const void *buf, uint16_t len) +{ + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAYER_ATTRS_TEXT, HAL_STATUS_FAILED); +} + +static void handle_get_player_values_text(const void *buf, uint16_t len) +{ + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAYER_VALUES_TEXT, HAL_STATUS_FAILED); +} + +static size_t write_element_text(uint8_t id, uint8_t text_len, uint8_t *text, + uint8_t *pdu) +{ + uint16_t charset = 106; + size_t len = 0; + + put_be32(id, pdu); + pdu += 4; + len += 4; + + put_be16(charset, pdu); + pdu += 2; + len += 2; + + put_be16(text_len, pdu); + pdu += 2; + len += 2; + + memcpy(pdu, text, text_len); + len += text_len; + + return len; +} + +static void write_element_attrs(uint8_t *ptr, uint8_t number, uint8_t *pdu, + size_t *len) +{ + int i; + + *pdu = number; + pdu++; + *len += 1; + + for (i = 0; i < number; i++) { + struct hal_avrcp_player_setting_text *text = (void *) ptr; + size_t ret; + + ret = write_element_text(text->id, text->len, text->text, pdu); + + ptr += sizeof(*text) + text->len; + pdu += ret; + *len += ret; + } +} + +static void handle_get_element_attrs_text(const void *buf, uint16_t len) +{ + struct hal_cmd_avrcp_get_element_attrs_text *cmd = (void *) buf; + uint8_t status; + struct avrcp_request *req; + uint8_t pdu[IPC_MTU]; + uint8_t *ptr; + size_t pdu_len; + int ret; + + DBG(""); + + req = pop_request(AVRCP_GET_ELEMENT_ATTRIBUTES, 0, false); + if (!req) { + status = HAL_STATUS_FAILED; + goto done; + } + + ptr = (uint8_t *) &cmd->values[0]; + pdu_len = 0; + write_element_attrs(ptr, cmd->number, pdu, &pdu_len); + + ret = avrcp_get_element_attrs_rsp(req->dev->session, req->transaction, + pdu, pdu_len); + if (ret < 0) { + status = HAL_STATUS_FAILED; + g_free(req); + goto done; + } + + status = HAL_STATUS_SUCCESS; + g_free(req); + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_ELEMENT_ATTRS_TEXT, status); +} + +static void handle_set_player_attrs_value(const void *buf, uint16_t len) +{ + DBG(""); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_SET_PLAYER_ATTRS_VALUE, HAL_STATUS_FAILED); +} + +static void handle_register_notification(const void *buf, uint16_t len) +{ + struct hal_cmd_avrcp_register_notification *cmd = (void *) buf; + uint8_t status; + struct avrcp_request *req; + uint8_t code; + bool peek = false; + int ret; + + DBG(""); + + switch (cmd->type) { + case HAL_AVRCP_EVENT_TYPE_INTERIM: + code = AVC_CTYPE_INTERIM; + peek = true; + break; + case HAL_AVRCP_EVENT_TYPE_CHANGED: + code = AVC_CTYPE_CHANGED; + break; + default: + status = HAL_STATUS_FAILED; + goto done; + } + + req = pop_request(AVRCP_REGISTER_NOTIFICATION, cmd->event, peek); + if (!req) { + status = HAL_STATUS_FAILED; + goto done; + } + + ret = avrcp_register_notification_rsp(req->dev->session, + req->transaction, code, + cmd->event, cmd->data, + cmd->len); + if (ret < 0) { + status = HAL_STATUS_FAILED; + if (!peek) + g_free(req); + goto done; + } + + status = HAL_STATUS_SUCCESS; + if (!peek) + g_free(req); + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, status); +} + +static void handle_set_volume(const void *buf, uint16_t len) +{ + struct hal_cmd_avrcp_set_volume *cmd = (void *) buf; + struct avrcp_device *dev; + uint8_t status; + int ret; + + DBG(""); + + if (!devices) { + error("AVRCP: No device found to set volume"); + status = HAL_STATUS_FAILED; + goto done; + } + + /* + * Peek the first device since the HAL cannot really address a specific + * device it might mean there could only be one connected. + */ + dev = devices->data; + + ret = avrcp_set_volume(dev->session, cmd->value & 0x7f); + if (ret < 0) { + status = HAL_STATUS_FAILED; + goto done; + } + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, HAL_OP_AVRCP_SET_VOLUME, + status); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_AVRCP_GET_PLAY_STATUS */ + { handle_get_play_status, false, + sizeof(struct hal_cmd_avrcp_get_play_status) }, + /* HAL_OP_AVRCP_LIST_PLAYER_ATTRS */ + { handle_list_player_attrs, true, + sizeof(struct hal_cmd_avrcp_list_player_attrs) }, + /* HAL_OP_AVRCP_LIST_PLAYER_VALUES */ + { handle_list_player_values, true, + sizeof(struct hal_cmd_avrcp_list_player_values) }, + /* HAL_OP_AVRCP_GET_PLAYER_ATTRS */ + { handle_get_player_attrs, true, + sizeof(struct hal_cmd_avrcp_get_player_attrs) }, + /* HAL_OP_AVRCP_GET_PLAYER_ATTRS_TEXT */ + { handle_get_player_attrs_text, true, + sizeof(struct hal_cmd_avrcp_get_player_attrs_text) }, + /* HAL_OP_AVRCP_GET_PLAYER_VALUES_TEXT */ + { handle_get_player_values_text, true, + sizeof(struct hal_cmd_avrcp_get_player_values_text) }, + /* HAL_OP_AVRCP_GET_ELEMENT_ATTRS_TEXT */ + { handle_get_element_attrs_text, true, + sizeof(struct hal_cmd_avrcp_get_element_attrs_text) }, + /* HAL_OP_AVRCP_SET_PLAYER_ATTRS_VALUE */ + { handle_set_player_attrs_value, true, + sizeof(struct hal_cmd_avrcp_set_player_attrs_value) }, + /* HAL_OP_AVRCP_REGISTER_NOTIFICATION */ + { handle_register_notification, true, + sizeof(struct hal_cmd_avrcp_register_notification) }, + /* HAL_OP_AVRCP_SET_VOLUME */ + { handle_set_volume, false, sizeof(struct hal_cmd_avrcp_set_volume) }, +}; + +static sdp_record_t *avrcp_tg_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrtg; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto_control, *proto_control[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = L2CAP_PSM_AVCTP; + uint16_t avrcp_ver = 0x0105, avctp_ver = 0x0104; + uint16_t feat = (AVRCP_FEATURE_CATEGORY_1 | + AVRCP_FEATURE_CATEGORY_2 | + AVRCP_FEATURE_CATEGORY_3 | + AVRCP_FEATURE_CATEGORY_4); + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &avrtg); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto_control[0] = sdp_list_append(NULL, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto_control[0] = sdp_list_append(proto_control[0], psm); + apseq = sdp_list_append(NULL, proto_control[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto_control[1] = sdp_list_append(NULL, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto_control[1] = sdp_list_append(proto_control[1], version); + apseq = sdp_list_append(apseq, proto_control[1]); + + aproto_control = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto_control); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP TG", NULL, NULL); + + sdp_data_free(psm); + sdp_data_free(version); + sdp_list_free(proto_control[0], NULL); + sdp_list_free(proto_control[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto_control, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static sdp_record_t *avrcp_ct_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrct, avrctr; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_CONTROL_PSM; + uint16_t avrcp_ver = 0x0105, avctp_ver = 0x0104; + uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | + AVRCP_FEATURE_CATEGORY_2 | + AVRCP_FEATURE_CATEGORY_3 | + AVRCP_FEATURE_CATEGORY_4); + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &avrct); + sdp_uuid16_create(&avrctr, AV_REMOTE_CONTROLLER_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &avrctr); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(NULL, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP CT", NULL, NULL); + + free(psm); + free(version); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static void avrcp_device_free(void *data) +{ + struct avrcp_device *dev = data; + + if (dev->queue) { + g_queue_foreach(dev->queue, (GFunc) g_free, NULL); + g_queue_free(dev->queue); + } + + if (dev->session) + avrcp_shutdown(dev->session); + + if (dev->io) { + g_io_channel_shutdown(dev->io, FALSE, NULL); + g_io_channel_unref(dev->io); + } + + g_free(dev); +} + +static void avrcp_device_remove(struct avrcp_device *dev) +{ + devices = g_slist_remove(devices, dev); + avrcp_device_free(dev); +} + +static struct avrcp_device *avrcp_device_new(const bdaddr_t *dst) +{ + struct avrcp_device *dev; + + dev = g_new0(struct avrcp_device, 1); + bacpy(&dev->dst, dst); + devices = g_slist_prepend(devices, dev); + + return dev; +} + +static int device_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct avrcp_device *dev = s; + const bdaddr_t *dst = user_data; + + return bacmp(&dev->dst, dst); +} + +static struct avrcp_device *avrcp_device_find(const bdaddr_t *dst) +{ + GSList *l; + + l = g_slist_find_custom(devices, dst, device_cmp); + if (!l) + return NULL; + + return l->data; +} + +static void disconnect_cb(void *data) +{ + struct avrcp_device *dev = data; + + DBG(""); + + dev->session = NULL; + + avrcp_device_remove(dev); +} + +static bool handle_fast_forward(struct avrcp *session, bool pressed, + void *user_data) +{ + struct hal_ev_avrcp_passthrough_cmd ev; + + DBG("pressed %s", pressed ? "true" : "false"); + + ev.id = AVC_FAST_FORWARD; + ev.state = pressed; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_PASSTHROUGH_CMD, sizeof(ev), &ev); + + return true; +} + +static bool handle_rewind(struct avrcp *session, bool pressed, + void *user_data) +{ + struct hal_ev_avrcp_passthrough_cmd ev; + + DBG("pressed %s", pressed ? "true" : "false"); + + ev.id = AVC_REWIND; + ev.state = pressed; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_PASSTHROUGH_CMD, sizeof(ev), &ev); + + return true; +} + +static const struct avrcp_passthrough_handler passthrough_handlers[] = { + { AVC_FAST_FORWARD, handle_fast_forward }, + { AVC_REWIND, handle_rewind }, + { }, +}; + +static int handle_get_capabilities_cmd(struct avrcp *session, + uint8_t transaction, void *user_data) +{ + uint8_t events[] = { AVRCP_EVENT_STATUS_CHANGED, + AVRCP_EVENT_TRACK_CHANGED, + AVRCP_EVENT_PLAYBACK_POS_CHANGED }; + + DBG(""); + + /* + * Android do not provide this info via HAL so the list most + * be hardcoded according to what RegisterNotification can + * actually handle + */ + avrcp_get_capabilities_rsp(session, transaction, sizeof(events), + events); + + return 0; +} + +static void push_request(struct avrcp_device *dev, uint8_t pdu_id, + uint8_t event_id, uint8_t transaction) +{ + struct avrcp_request *req; + + req = g_new0(struct avrcp_request, 1); + req->dev = dev; + req->pdu_id = pdu_id; + req->event_id = event_id; + req->transaction = transaction; + + g_queue_push_tail(dev->queue, req); +} + +static int handle_get_play_status_cmd(struct avrcp *session, + uint8_t transaction, void *user_data) +{ + struct avrcp_device *dev = user_data; + + DBG(""); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_GET_PLAY_STATUS, 0, NULL); + + push_request(dev, AVRCP_GET_PLAY_STATUS, 0, transaction); + + return 0; +} + +static int handle_get_element_attrs_cmd(struct avrcp *session, + uint8_t transaction, uint64_t uid, + uint8_t number, uint32_t *attrs, + void *user_data) +{ + struct avrcp_device *dev = user_data; + uint8_t buf[IPC_MTU]; + struct hal_ev_avrcp_get_element_attrs *ev = (void *) buf; + int i; + + DBG(""); + + ev->number = number; + /* Set everything in case of empty list */ + if (ev->number == 0) { + for (i = 0; i < HAL_AVRCP_MEDIA_ATTR_DURATION; i++) { + /* Skip 0x00 as the attributes start with 0x01 */ + ev->attrs[i] = i + 1; + } + ev->number = i; + goto done; + } + + for (i = 0; i < number; i++) + ev->attrs[i] = attrs[i]; + +done: + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_GET_ELEMENT_ATTRS, + sizeof(*ev) + ev->number, ev); + + push_request(dev, AVRCP_GET_ELEMENT_ATTRIBUTES, 0, transaction); + + return 0; + +} + +static int handle_register_notification_cmd(struct avrcp *session, + uint8_t transaction, + uint8_t event, + uint32_t interval, + void *user_data) +{ + struct avrcp_device *dev = user_data; + struct hal_ev_avrcp_register_notification ev; + + DBG(""); + + /* TODO: Add any missing events supported by Android */ + switch (event) { + case AVRCP_EVENT_STATUS_CHANGED: + case AVRCP_EVENT_TRACK_CHANGED: + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + break; + default: + return -EINVAL; + } + + ev.event = event; + ev.param = interval; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_REGISTER_NOTIFICATION, + sizeof(ev), &ev); + + push_request(dev, AVRCP_REGISTER_NOTIFICATION, event, transaction); + + return 0; +} + +static const struct avrcp_control_ind control_ind = { + .get_capabilities = handle_get_capabilities_cmd, + .get_play_status = handle_get_play_status_cmd, + .get_element_attributes = handle_get_element_attrs_cmd, + .register_notification = handle_register_notification_cmd, +}; + +static bool handle_register_notification_rsp(struct avrcp *session, int err, + uint8_t code, uint8_t event, + void *params, + void *user_data) +{ + struct avrcp_device *dev = user_data; + struct hal_ev_avrcp_volume_changed ev; + uint8_t *volume = params; + + if (err < 0) { + error("AVRCP: %s", strerror(-err)); + return false; + } + + if (code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED) + return false; + + if (event != AVRCP_EVENT_VOLUME_CHANGED) + return false; + + ev.type = code; + ev.volume = volume[0]; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_VOLUME_CHANGED, + sizeof(ev), &ev); + + if (code == AVC_CTYPE_INTERIM) + return true; + + avrcp_register_notification(dev->session, event, 0); + return false; +} + +static void handle_get_capabilities_rsp(struct avrcp *session, int err, + uint8_t number, uint8_t *events, + void *user_data) +{ + struct avrcp_device *dev = user_data; + int i; + + if (err < 0) { + error("AVRCP: %s", strerror(-err)); + return; + } + + for (i = 0; i < number; i++) { + if (events[i] != AVRCP_EVENT_VOLUME_CHANGED) + continue; + + avrcp_register_notification(dev->session, events[i], 0); + break; + } + + return; +} + +static void handle_set_volume_rsp(struct avrcp *session, int err, + uint8_t value, void *user_data) +{ + struct hal_ev_avrcp_volume_changed ev; + + if (err < 0) { + ev.volume = 0; + ev.type = AVC_CTYPE_REJECTED; + goto done; + } + + ev.volume = value; + ev.type = AVC_CTYPE_ACCEPTED; + +done: + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_VOLUME_CHANGED, + sizeof(ev), &ev); +} + +static const struct avrcp_control_cfm control_cfm = { + .get_capabilities = handle_get_capabilities_rsp, + .register_notification = handle_register_notification_rsp, + .set_volume = handle_set_volume_rsp, +}; + +static int avrcp_device_add_session(struct avrcp_device *dev, int fd, + uint16_t imtu, uint16_t omtu) +{ + struct hal_ev_avrcp_remote_features ev; + char address[18]; + + dev->session = avrcp_new(fd, imtu, omtu, dev->version); + if (!dev->session) + return -EINVAL; + + avrcp_set_destroy_cb(dev->session, disconnect_cb, dev); + avrcp_set_passthrough_handlers(dev->session, passthrough_handlers, + dev); + avrcp_register_player(dev->session, &control_ind, &control_cfm, dev); + + dev->queue = g_queue_new(); + + ba2str(&dev->dst, address); + + /* FIXME: get the real name of the device */ + avrcp_init_uinput(dev->session, "bluetooth", address); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.features = HAL_AVRCP_FEATURE_NONE; + + DBG("version 0x%02x", dev->version); + + if (dev->version < 0x0103) + goto done; + + ev.features |= HAL_AVRCP_FEATURE_METADATA; + + if (dev->version < 0x0104) + goto done; + + ev.features |= HAL_AVRCP_FEATURE_ABSOLUTE_VOLUME; + + avrcp_get_capabilities(dev->session, CAP_EVENTS_SUPPORTED); + +done: + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP, + HAL_EV_AVRCP_REMOTE_FEATURES, + sizeof(ev), &ev); + + return 0; +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct avrcp_device *dev = user_data; + uint16_t imtu, omtu; + char address[18]; + GError *gerr = NULL; + int fd; + + if (err) { + error("%s", err->message); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_DEST, address, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + fd = g_io_channel_unix_get_fd(chan); + if (avrcp_device_add_session(dev, fd, imtu, omtu) < 0) { + avrcp_device_free(dev); + return; + } + + g_io_channel_set_close_on_unref(chan, FALSE); + + if (dev->io) { + g_io_channel_unref(dev->io); + dev->io = NULL; + } + + DBG("%s connected", address); +} + +static bool avrcp_device_connect(struct avrcp_device *dev, BtIOConnect cb) +{ + GError *err = NULL; + + dev->io = bt_io_connect(cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_AVCTP, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + return false; + } + + return true; +} + +static void search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct avrcp_device *dev = data; + sdp_list_t *list; + + DBG(""); + + if (!g_slist_find(devices, dev)) + return; + + if (err < 0) { + error("Unable to get AV_REMOTE_SVCLASS_ID SDP record: %s", + strerror(-err)); + goto fail; + } + + if (!recs || !recs->data) { + error("No AVRCP records found"); + goto fail; + } + + for (list = recs; list; list = list->next) { + sdp_record_t *rec = list->data; + sdp_list_t *l; + sdp_profile_desc_t *desc; + int features; + + if (sdp_get_profile_descs(rec, &l) < 0) + continue; + + desc = l->data; + dev->version = desc->version; + + if (sdp_get_int_attr(rec, SDP_ATTR_SUPPORTED_FEATURES, + &features) == 0) + dev->features = features; + + sdp_list_free(l, free); + break; + } + + if (dev->io) { + GError *gerr = NULL; + if (!bt_io_accept(dev->io, connect_cb, dev, NULL, &gerr)) { + error("bt_io_accept: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + return; + } + + if (!avrcp_device_connect(dev, connect_cb)) { + error("Unable to connect to AVRCP"); + goto fail; + } + + return; + +fail: + avrcp_device_remove(dev); +} + +static int avrcp_device_search(struct avrcp_device *dev) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, AV_REMOTE_SVCLASS_ID); + + return bt_search_service(&adapter_addr, &dev->dst, &uuid, search_cb, + dev, NULL, 0); +} + +static void confirm_cb(GIOChannel *chan, gpointer data) +{ + struct avrcp_device *dev; + char address[18]; + bdaddr_t dst; + GError *err = NULL; + + bt_io_get(chan, &err, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + DBG("incoming connect from %s", address); + + dev = avrcp_device_find(&dst); + if (dev && dev->session) { + error("AVRCP: Refusing unexpected connect"); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + dev = avrcp_device_new(&dst); + if (avrcp_device_search(dev) < 0) { + error("AVRCP: Failed to search SDP details"); + avrcp_device_free(dev); + g_io_channel_shutdown(chan, TRUE, NULL); + } + + dev->io = g_io_channel_ref(chan); +} + +bool bt_avrcp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + GError *err = NULL; + sdp_record_t *rec; + + DBG(""); + + bacpy(&adapter_addr, addr); + + server = bt_io_listen(NULL, confirm_cb, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_PSM, L2CAP_PSM_AVCTP, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (!server) { + error("Failed to listen on AVDTP channel: %s", err->message); + g_error_free(err); + return false; + } + + rec = avrcp_tg_record(); + if (!rec) { + error("Failed to allocate AVRCP TG record"); + goto fail; + } + + if (bt_adapter_add_record(rec, 0) < 0) { + error("Failed to register AVRCP TG record"); + sdp_record_free(rec); + goto fail; + } + record_tg_id = rec->handle; + + rec = avrcp_ct_record(); + if (!rec) { + error("Failed to allocate AVRCP CT record"); + bt_adapter_remove_record(record_tg_id); + goto fail; + } + + if (bt_adapter_add_record(rec, 0) < 0) { + error("Failed to register AVRCP CT record"); + bt_adapter_remove_record(record_tg_id); + sdp_record_free(rec); + goto fail; + } + record_ct_id = rec->handle; + + hal_ipc = ipc; + + ipc_register(hal_ipc, HAL_SERVICE_ID_AVRCP, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; +fail: + g_io_channel_shutdown(server, TRUE, NULL); + g_io_channel_unref(server); + server = NULL; + + return false; +} + +void bt_avrcp_unregister(void) +{ + DBG(""); + + g_slist_free_full(devices, avrcp_device_free); + devices = NULL; + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_AVRCP); + hal_ipc = NULL; + + bt_adapter_remove_record(record_tg_id); + record_tg_id = 0; + + bt_adapter_remove_record(record_ct_id); + record_ct_id = 0; + + if (server) { + g_io_channel_shutdown(server, TRUE, NULL); + g_io_channel_unref(server); + server = NULL; + } +} + +void bt_avrcp_connect(const bdaddr_t *dst) +{ + struct avrcp_device *dev; + char addr[18]; + + DBG(""); + + if (avrcp_device_find(dst)) + return; + + dev = avrcp_device_new(dst); + if (avrcp_device_search(dev) < 0) { + error("AVRCP: Failed to search SDP details"); + avrcp_device_free(dev); + } + + ba2str(&dev->dst, addr); + DBG("connecting to %s", addr); +} + +void bt_avrcp_disconnect(const bdaddr_t *dst) +{ + struct avrcp_device *dev; + + DBG(""); + + dev = avrcp_device_find(dst); + if (!dev) + return; + + if (dev->session) { + avrcp_shutdown(dev->session); + return; + } + + avrcp_device_remove(dev); +}
diff --git a/android/avrcp.h b/android/avrcp.h new file mode 100644 index 0000000..11e79b7 --- /dev/null +++ b/android/avrcp.h
@@ -0,0 +1,28 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_avrcp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_avrcp_unregister(void); + +void bt_avrcp_connect(const bdaddr_t *dst); +void bt_avrcp_disconnect(const bdaddr_t *dst);
diff --git a/android/bluetooth.c b/android/bluetooth.c new file mode 100644 index 0000000..51a31fe --- /dev/null +++ b/android/bluetooth.c
@@ -0,0 +1,5343 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/mgmt.h" +#include "lib/uuid.h" +#include "src/shared/util.h" +#include "src/shared/mgmt.h" +#include "src/shared/queue.h" +#include "src/eir.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "src/sdp-client.h" +#include "src/sdpd.h" +#include "src/log.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "utils.h" +#include "bluetooth.h" + +#define DUT_MODE_FILE "/sys/kernel/debug/bluetooth/hci%u/dut_mode" + +#define SETTINGS_FILE ANDROID_STORAGEDIR"/settings" +#define DEVICES_FILE ANDROID_STORAGEDIR"/devices" +#define CACHE_FILE ANDROID_STORAGEDIR"/cache" + +#define ADAPTER_MAJOR_CLASS 0x02 /* Phone */ +#define ADAPTER_MINOR_CLASS 0x03 /* Smartphone */ + +/* Default to DisplayYesNo */ +#define DEFAULT_IO_CAPABILITY 0x01 + +/* Default discoverable timeout 120sec as in Android */ +#define DEFAULT_DISCOVERABLE_TIMEOUT 120 + +#define DEVICES_CACHE_MAX 300 + +#define BASELEN_PROP_CHANGED (sizeof(struct hal_ev_adapter_props_changed) \ + + sizeof(struct hal_property)) + +#define BASELEN_REMOTE_DEV_PROP (sizeof(struct hal_ev_remote_device_props) \ + + sizeof(struct hal_property)) + +#define SCAN_TYPE_NONE 0 +#define SCAN_TYPE_BREDR (1 << BDADDR_BREDR) +#define SCAN_TYPE_LE ((1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM)) +#define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE) + +#define BDADDR_LE (BDADDR_LE_RANDOM | BDADDR_LE_PUBLIC) + +struct device { + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + bdaddr_t rpa; + uint8_t rpa_type; + + bool le; + bool bredr; + + bool pairing; + + bool bredr_paired; + bool bredr_bonded; + bool le_paired; + bool le_bonded; + + bool in_white_list; + + bool connected; + + char *name; + char *friendly_name; + + uint32_t class; + int32_t rssi; + + time_t bredr_seen; + time_t le_seen; + + GSList *uuids; + + bool found; /* if device is found in current discovery session */ + unsigned int confirm_id; /* mgtm command id if command pending */ + + bool valid_remote_csrk; + bool remote_csrk_auth; + uint8_t remote_csrk[16]; + uint32_t remote_sign_cnt; + + bool valid_local_csrk; + bool local_csrk_auth; + uint8_t local_csrk[16]; + uint32_t local_sign_cnt; + uint16_t gatt_ccc; +}; + +struct browse_req { + bdaddr_t bdaddr; + GSList *uuids; + int search_uuid; + int reconnect_attempt; +}; + +static struct { + uint16_t index; + + bdaddr_t bdaddr; + uint32_t dev_class; + + char *name; + + uint8_t max_advert_instance; + uint8_t rpa_offload_supported; + uint8_t max_irk_list_size; + uint8_t max_scan_filters_supported; + uint16_t scan_result_storage_size; + uint8_t activity_energy_info_supported; + + uint32_t current_settings; + uint32_t supported_settings; + + bool le_scanning; + uint8_t cur_discovery_type; + uint8_t exp_discovery_type; + uint32_t discoverable_timeout; + + GSList *uuids; +} adapter = { + .index = MGMT_INDEX_NONE, + .dev_class = 0, + .name = NULL, + .max_advert_instance = 0, + .rpa_offload_supported = 0, + .max_irk_list_size = 0, + .max_scan_filters_supported = 0, + .scan_result_storage_size = 0, + .activity_energy_info_supported = 0, + .current_settings = 0, + .supported_settings = 0, + .cur_discovery_type = SCAN_TYPE_NONE, + .exp_discovery_type = SCAN_TYPE_NONE, + .discoverable_timeout = DEFAULT_DISCOVERABLE_TIMEOUT, + .uuids = NULL, +}; + +static const uint16_t uuid_list[] = { + L2CAP_UUID, + PNP_INFO_SVCLASS_ID, + PUBLIC_BROWSE_GROUP, + 0 +}; + +static uint16_t option_index = MGMT_INDEX_NONE; +static struct mgmt *mgmt_if = NULL; + +static GSList *bonded_devices = NULL; +static GSList *cached_devices = NULL; + +static bt_le_device_found gatt_device_found_cb = NULL; +static bt_le_discovery_stopped gatt_discovery_stopped_cb = NULL; + +/* This list contains addresses which are asked for records */ +static GSList *browse_reqs; + +static struct ipc *hal_ipc = NULL; + +static bool kernel_conn_control = false; + +static struct queue *unpaired_cb_list = NULL; +static struct queue *paired_cb_list = NULL; + +static void get_device_android_addr(struct device *dev, uint8_t *addr) +{ + /* + * If RPA is set it means that IRK was received and ID address is being + * used. Android Framework is still using old RPA and it needs to be + * used in notifications. + */ + if (bacmp(&dev->rpa, BDADDR_ANY)) + bdaddr2android(&dev->rpa, addr); + else + bdaddr2android(&dev->bdaddr, addr); +} + +static void mgmt_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + info("%s%s", prefix, str); +} + +static void store_adapter_config(void) +{ + GKeyFile *key_file; + gsize length = 0; + char addr[18]; + char *data; + + key_file = g_key_file_new(); + + g_key_file_load_from_file(key_file, SETTINGS_FILE, 0, NULL); + + ba2str(&adapter.bdaddr, addr); + + g_key_file_set_string(key_file, "General", "Address", addr); + + if (adapter.name) + g_key_file_set_string(key_file, "General", "Name", + adapter.name); + + g_key_file_set_integer(key_file, "General", "DiscoverableTimeout", + adapter.discoverable_timeout); + + data = g_key_file_to_data(key_file, &length, NULL); + + g_file_set_contents(SETTINGS_FILE, data, length, NULL); + + g_free(data); + g_key_file_free(key_file); +} + +static void load_adapter_config(void) +{ + GError *gerr = NULL; + GKeyFile *key_file; + char *str; + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, SETTINGS_FILE, 0, NULL); + + str = g_key_file_get_string(key_file, "General", "Address", NULL); + if (!str) { + g_key_file_free(key_file); + return; + } + + str2ba(str, &adapter.bdaddr); + g_free(str); + + adapter.name = g_key_file_get_string(key_file, "General", "Name", NULL); + + adapter.discoverable_timeout = g_key_file_get_integer(key_file, + "General", "DiscoverableTimeout", &gerr); + if (gerr) { + adapter.discoverable_timeout = DEFAULT_DISCOVERABLE_TIMEOUT; + g_clear_error(&gerr); + } + + g_key_file_free(key_file); +} + +static void store_device_info(struct device *dev, const char *path) +{ + GKeyFile *key_file; + char addr[18]; + gsize length = 0; + char **uuids = NULL; + char *str; + + ba2str(&dev->bdaddr, addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, path, 0, NULL); + + g_key_file_set_boolean(key_file, addr, "BREDR", dev->bredr); + + if (dev->le) + g_key_file_set_integer(key_file, addr, "AddressType", + dev->bdaddr_type); + + g_key_file_set_string(key_file, addr, "Name", dev->name); + + if (dev->friendly_name) + g_key_file_set_string(key_file, addr, "FriendlyName", + dev->friendly_name); + else + g_key_file_remove_key(key_file, addr, "FriendlyName", NULL); + + if (dev->class) + g_key_file_set_integer(key_file, addr, "Class", dev->class); + else + g_key_file_remove_key(key_file, addr, "Class", NULL); + + if (dev->bredr_seen > dev->le_seen) + g_key_file_set_integer(key_file, addr, "Timestamp", + dev->bredr_seen); + else + g_key_file_set_integer(key_file, addr, "Timestamp", + dev->le_seen); + + if (dev->uuids) { + GSList *l; + int i; + + uuids = g_new0(char *, g_slist_length(dev->uuids) + 1); + + for (i = 0, l = dev->uuids; l; l = g_slist_next(l), i++) { + int j; + uint8_t *u = l->data; + char *uuid_str = g_malloc0(33); + + for (j = 0; j < 16; j++) + sprintf(uuid_str + (j * 2), "%2.2X", u[j]); + + uuids[i] = uuid_str; + } + + g_key_file_set_string_list(key_file, addr, "Services", + (const char **)uuids, i); + } else { + g_key_file_remove_key(key_file, addr, "Services", NULL); + } + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(path, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); + g_strfreev(uuids); +} + +static void remove_device_info(struct device *dev, const char *path) +{ + GKeyFile *key_file; + gsize length = 0; + char addr[18]; + char *str; + + ba2str(&dev->bdaddr, addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, path, 0, NULL); + + g_key_file_remove_group(key_file, addr, NULL); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(path, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); +} + +static int device_match(gconstpointer a, gconstpointer b) +{ + const struct device *dev = a; + const bdaddr_t *bdaddr = b; + + /* Android is using RPA even if IRK was received and ID addr resolved */ + if (!bacmp(&dev->rpa, bdaddr)) + return 0; + + return bacmp(&dev->bdaddr, bdaddr); +} + +static struct device *find_device(const bdaddr_t *bdaddr) +{ + GSList *l; + + l = g_slist_find_custom(bonded_devices, bdaddr, device_match); + if (l) + return l->data; + + l = g_slist_find_custom(cached_devices, bdaddr, device_match); + if (l) + return l->data; + + return NULL; +} + +static void free_device(struct device *dev) +{ + if (dev->confirm_id) + mgmt_cancel(mgmt_if, dev->confirm_id); + + g_free(dev->name); + g_free(dev->friendly_name); + g_slist_free_full(dev->uuids, g_free); + g_free(dev); +} + +static void cache_device(struct device *new_dev) +{ + struct device *dev; + GSList *l; + + l = g_slist_find(cached_devices, new_dev); + if (l) { + cached_devices = g_slist_remove(cached_devices, new_dev); + goto cache; + } + + if (g_slist_length(cached_devices) < DEVICES_CACHE_MAX) + goto cache; + + l = g_slist_last(cached_devices); + dev = l->data; + + cached_devices = g_slist_remove(cached_devices, dev); + remove_device_info(dev, CACHE_FILE); + free_device(dev); + +cache: + cached_devices = g_slist_prepend(cached_devices, new_dev); + store_device_info(new_dev, CACHE_FILE); +} + +static struct device *create_device(const bdaddr_t *bdaddr, uint8_t bdaddr_type) +{ + struct device *dev; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("%s", addr); + + dev = g_new0(struct device, 1); + + bacpy(&dev->bdaddr, bdaddr); + + if (bdaddr_type == BDADDR_BREDR) { + dev->bredr = true; + dev->bredr_seen = time(NULL); + } else { + dev->le = true; + dev->bdaddr_type = bdaddr_type; + dev->le_seen = time(NULL); + } + + /* + * Use address for name, will be change if one is present + * eg. in EIR or set by set_property. + */ + dev->name = g_strdup(addr); + + return dev; +} + +static struct device *get_device(const bdaddr_t *bdaddr, uint8_t type) +{ + struct device *dev; + + dev = find_device(bdaddr); + if (dev) + return dev; + + dev = create_device(bdaddr, type); + + cache_device(dev); + + return dev; +} + +static struct device *find_device_android(const uint8_t *addr) +{ + bdaddr_t bdaddr; + + android2bdaddr(addr, &bdaddr); + + return find_device(&bdaddr); +} + +static struct device *get_device_android(const uint8_t *addr) +{ + bdaddr_t bdaddr; + + android2bdaddr(addr, &bdaddr); + + return get_device(&bdaddr, BDADDR_BREDR); +} + +static void send_adapter_property(uint8_t type, uint16_t len, const void *val) +{ + uint8_t buf[BASELEN_PROP_CHANGED + len]; + struct hal_ev_adapter_props_changed *ev = (void *) buf; + + ev->status = HAL_STATUS_SUCCESS; + ev->num_props = 1; + ev->props[0].type = type; + ev->props[0].len = len; + memcpy(ev->props[0].val, val, len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_ADAPTER_PROPS_CHANGED, sizeof(buf), buf); +} + +static void adapter_name_changed(const uint8_t *name) +{ + /* Android expects string value without NULL terminator */ + send_adapter_property(HAL_PROP_ADAPTER_NAME, + strlen((const char *) name), name); +} + +static void adapter_set_name(const uint8_t *name) +{ + if (!g_strcmp0(adapter.name, (const char *) name)) + return; + + DBG("%s", name); + + g_free(adapter.name); + adapter.name = g_strdup((const char *) name); + + store_adapter_config(); + + adapter_name_changed(name); +} + +static void mgmt_local_name_changed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_cp_set_local_name *rp = param; + + if (length < sizeof(*rp)) { + error("Wrong size of local name changed parameters"); + return; + } + + adapter_set_name(rp->name); + + /* TODO Update services if needed */ +} + +static void powered_changed(void) +{ + struct hal_ev_adapter_state_changed ev; + + ev.state = (adapter.current_settings & MGMT_SETTING_POWERED) ? + HAL_POWER_ON : HAL_POWER_OFF; + + DBG("%u", ev.state); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_ADAPTER_STATE_CHANGED, sizeof(ev), &ev); +} + +static uint8_t settings2scan_mode(void) +{ + bool connectable, discoverable; + + connectable = adapter.current_settings & MGMT_SETTING_CONNECTABLE; + discoverable = adapter.current_settings & MGMT_SETTING_DISCOVERABLE; + + if (connectable && discoverable) + return HAL_ADAPTER_SCAN_MODE_CONN_DISC; + + if (connectable) + return HAL_ADAPTER_SCAN_MODE_CONN; + + return HAL_ADAPTER_SCAN_MODE_NONE; +} + +static void scan_mode_changed(void) +{ + uint8_t mode; + + mode = settings2scan_mode(); + + DBG("mode %u", mode); + + send_adapter_property(HAL_PROP_ADAPTER_SCAN_MODE, sizeof(mode), &mode); +} + +static void adapter_class_changed(void) +{ + send_adapter_property(HAL_PROP_ADAPTER_CLASS, sizeof(adapter.dev_class), + &adapter.dev_class); +} + +static void settings_changed(uint32_t settings) +{ + uint32_t changed_mask; + uint32_t scan_mode_mask; + + changed_mask = adapter.current_settings ^ settings; + + adapter.current_settings = settings; + + DBG("0x%08x", changed_mask); + + if (changed_mask & MGMT_SETTING_POWERED) + powered_changed(); + + scan_mode_mask = MGMT_SETTING_CONNECTABLE | + MGMT_SETTING_DISCOVERABLE; + + /* + * Only when powered, the connectable and discoverable + * state changes should be communicated. + */ + if (adapter.current_settings & MGMT_SETTING_POWERED) + if (changed_mask & scan_mode_mask) + scan_mode_changed(); +} + +static void new_settings_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + uint32_t settings; + + if (length < sizeof(settings)) { + error("Wrong size of new settings parameters"); + return; + } + + settings = get_le32(param); + + DBG("settings: 0x%8.8x -> 0x%8.8x", adapter.current_settings, + settings); + + if (settings == adapter.current_settings) + return; + + settings_changed(settings); +} + +static void mgmt_dev_class_changed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_cod *rp = param; + uint32_t dev_class; + + if (length < sizeof(*rp)) { + error("Wrong size of class of device changed parameters"); + return; + } + + dev_class = rp->val[0] | (rp->val[1] << 8) | (rp->val[2] << 16); + + if (dev_class == adapter.dev_class) + return; + + DBG("Class: 0x%06x", dev_class); + + adapter.dev_class = dev_class; + + adapter_class_changed(); + + /* TODO: Gatt attrib set*/ +} + +void bt_store_gatt_ccc(const bdaddr_t *dst, uint16_t value) +{ + struct device *dev; + GKeyFile *key_file; + gsize length = 0; + char addr[18]; + char *data; + + dev = find_device(dst); + if (!dev) + return; + + key_file = g_key_file_new(); + + if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) { + g_key_file_free(key_file); + return; + } + + ba2str(&dev->bdaddr, addr); + + DBG("%s Gatt CCC %d", addr, value); + + g_key_file_set_integer(key_file, addr, "GattCCC", value); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(DEVICES_FILE, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); + + dev->gatt_ccc = value; +} + +uint16_t bt_get_gatt_ccc(const bdaddr_t *addr) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return 0; + + return dev->gatt_ccc; +} + +static void store_link_key(const bdaddr_t *dst, const uint8_t *key, + uint8_t type, uint8_t pin_length) +{ + GKeyFile *key_file; + char key_str[33]; + gsize length = 0; + char addr[18]; + char *data; + int i; + + key_file = g_key_file_new(); + + if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) { + g_key_file_free(key_file); + return; + } + + ba2str(dst, addr); + + DBG("%s type %u pin_len %u", addr, type, pin_length); + + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", key[i]); + + g_key_file_set_string(key_file, addr, "LinkKey", key_str); + g_key_file_set_integer(key_file, addr, "LinkKeyType", type); + g_key_file_set_integer(key_file, addr, "LinkKeyPinLength", pin_length); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(DEVICES_FILE, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +static void send_bond_state_change(struct device *dev, uint8_t status, + uint8_t state) +{ + struct hal_ev_bond_state_changed ev; + + ev.status = status; + ev.state = state; + get_device_android_addr(dev, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_BOND_STATE_CHANGED, sizeof(ev), &ev); +} + +static void update_bredr_state(struct device *dev, bool pairing, bool paired, + bool bonded) +{ + if (pairing == dev->pairing && paired == dev->bredr_paired && + bonded == dev->bredr_bonded) + return; + + /* avoid unpairing device on incoming pairing request */ + if (pairing && dev->bredr_paired) + goto done; + + /* avoid unpairing device if pairing failed */ + if (!pairing && !paired && dev->pairing && dev->bredr_paired) + goto done; + + if (paired && !dev->le_paired && !dev->bredr_paired) { + cached_devices = g_slist_remove(cached_devices, dev); + bonded_devices = g_slist_prepend(bonded_devices, dev); + remove_device_info(dev, CACHE_FILE); + store_device_info(dev, DEVICES_FILE); + } else if (!paired && !dev->le_paired) { + bonded_devices = g_slist_remove(bonded_devices, dev); + remove_device_info(dev, DEVICES_FILE); + cache_device(dev); + } + + dev->bredr_paired = paired; + + if (dev->bredr_paired) + dev->bredr_bonded = dev->bredr_bonded || bonded; + else + dev->bredr_bonded = false; + +done: + dev->pairing = pairing; +} + +static void update_le_state(struct device *dev, bool pairing, bool paired, + bool bonded) +{ + if (pairing == dev->pairing && paired == dev->le_paired && + bonded == dev->le_bonded) + return; + + /* avoid unpairing device on incoming pairing request */ + if (pairing && dev->le_paired) + goto done; + + /* avoid unpairing device if pairing failed */ + if (!pairing && !paired && dev->pairing && dev->le_paired) + goto done; + + if (paired && !dev->bredr_paired && !dev->le_paired) { + cached_devices = g_slist_remove(cached_devices, dev); + bonded_devices = g_slist_prepend(bonded_devices, dev); + remove_device_info(dev, CACHE_FILE); + store_device_info(dev, DEVICES_FILE); + } else if (!paired && !dev->bredr_paired) { + bonded_devices = g_slist_remove(bonded_devices, dev); + remove_device_info(dev, DEVICES_FILE); + dev->valid_local_csrk = false; + dev->valid_remote_csrk = false; + dev->local_sign_cnt = 0; + dev->remote_sign_cnt = 0; + memset(dev->local_csrk, 0, sizeof(dev->local_csrk)); + memset(dev->remote_csrk, 0, sizeof(dev->remote_csrk)); + cache_device(dev); + } + + dev->le_paired = paired; + + if (dev->le_paired) + dev->le_bonded = dev->le_bonded || bonded; + else + dev->le_bonded = false; + +done: + dev->pairing = pairing; +} + +static uint8_t device_bond_state(struct device *dev) +{ + if (dev->pairing) + return HAL_BOND_STATE_BONDING; + + /* + * We are checking for paired here instead of bonded as HAL API is + * using BOND state also if there was no bonding pairing. + */ + if (dev->bredr_paired || dev->le_paired) + return HAL_BOND_STATE_BONDED; + + return HAL_BOND_STATE_NONE; +} + +static void update_bond_state(struct device *dev, uint8_t status, + uint8_t old_bond, uint8_t new_bond) +{ + if (old_bond == new_bond) + return; + + /* + * When internal bond state changes from bond to non-bond or other way, + * BfA needs to send bonding state to Android in the middle. Otherwise + * Android will not handle it correctly + */ + if ((old_bond == HAL_BOND_STATE_NONE && + new_bond == HAL_BOND_STATE_BONDED) || + (old_bond == HAL_BOND_STATE_BONDED && + new_bond == HAL_BOND_STATE_NONE)) + send_bond_state_change(dev, HAL_STATUS_SUCCESS, + HAL_BOND_STATE_BONDING); + + send_bond_state_change(dev, status, new_bond); +} + +static void send_paired_notification(void *data, void *user_data) +{ + bt_paired_device_cb cb = data; + struct device *dev = user_data; + + cb(&dev->bdaddr); +} + +static void update_device_state(struct device *dev, uint8_t addr_type, + uint8_t status, bool pairing, bool paired, + bool bonded) +{ + uint8_t old_bond, new_bond; + + old_bond = device_bond_state(dev); + + if (addr_type == BDADDR_BREDR) + update_bredr_state(dev, pairing, paired, bonded); + else + update_le_state(dev, pairing, paired, bonded); + + new_bond = device_bond_state(dev); + + update_bond_state(dev, status, old_bond, new_bond); +} + +static void send_device_property(struct device *dev, uint8_t type, + uint16_t len, const void *val) +{ + uint8_t buf[BASELEN_REMOTE_DEV_PROP + len]; + struct hal_ev_remote_device_props *ev = (void *) buf; + + ev->status = HAL_STATUS_SUCCESS; + get_device_android_addr(dev, ev->bdaddr); + ev->num_props = 1; + ev->props[0].type = type; + ev->props[0].len = len; + memcpy(ev->props[0].val, val, len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_REMOTE_DEVICE_PROPS, sizeof(buf), buf); +} + +static void send_device_uuids_notif(struct device *dev) +{ + uint8_t buf[sizeof(uint128_t) * g_slist_length(dev->uuids)]; + uint8_t *ptr = buf; + GSList *l; + + for (l = dev->uuids; l; l = g_slist_next(l)) { + memcpy(ptr, l->data, sizeof(uint128_t)); + ptr += sizeof(uint128_t); + } + + send_device_property(dev, HAL_PROP_DEVICE_UUIDS, sizeof(buf), buf); +} + +static void set_device_uuids(struct device *dev, GSList *uuids) +{ + g_slist_free_full(dev->uuids, g_free); + dev->uuids = uuids; + + if (dev->le_paired || dev->bredr_paired) + store_device_info(dev, DEVICES_FILE); + else + store_device_info(dev, CACHE_FILE); + + send_device_uuids_notif(dev); +} + +static void browse_req_free(struct browse_req *req) +{ + g_slist_free_full(req->uuids, g_free); + g_free(req); +} + +static int uuid_128_cmp(gconstpointer a, gconstpointer b) +{ + return memcmp(a, b, sizeof(uint128_t)); +} + +static void update_records(struct browse_req *req, sdp_list_t *recs) +{ + for (; recs; recs = recs->next) { + sdp_record_t *rec = (sdp_record_t *) recs->data; + sdp_list_t *svcclass = NULL; + uuid_t uuid128; + uuid_t *tmp; + uint8_t *new_uuid; + + if (!rec) + break; + + if (sdp_get_service_classes(rec, &svcclass) < 0) + continue; + + if (!svcclass) + continue; + + tmp = svcclass->data; + + switch (tmp->type) { + case SDP_UUID16: + sdp_uuid16_to_uuid128(&uuid128, tmp); + break; + case SDP_UUID32: + sdp_uuid32_to_uuid128(&uuid128, tmp); + break; + case SDP_UUID128: + memcpy(&uuid128, tmp, sizeof(uuid_t)); + break; + default: + sdp_list_free(svcclass, free); + continue; + } + + new_uuid = g_malloc(16);/* size of 128 bit uuid */ + memcpy(new_uuid, &uuid128.value.uuid128, + sizeof(uuid128.value.uuid128)); + + /* Check if uuid is already added */ + if (g_slist_find_custom(req->uuids, new_uuid, uuid_128_cmp)) + g_free(new_uuid); + else + req->uuids = g_slist_append(req->uuids, new_uuid); + + sdp_list_free(svcclass, free); + } +} + +static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct browse_req *req = user_data; + struct device *dev; + 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) { + if (err == -ECONNRESET && req->reconnect_attempt < 1) { + req->search_uuid--; + req->reconnect_attempt++; + } else { + goto done; + } + } + + update_records(req, recs); + + /* Search for mandatory uuids */ + if (uuid_list[req->search_uuid]) { + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); + bt_search_service(&adapter.bdaddr, &req->bdaddr, &uuid, + browse_cb, user_data, NULL, 0); + return; + } + +done: + dev = find_device(&req->bdaddr); + if (dev) { + set_device_uuids(dev, req->uuids); + req->uuids = NULL; + } + + browse_reqs = g_slist_remove(browse_reqs, req); + browse_req_free(req); +} + +static int req_cmp(gconstpointer a, gconstpointer b) +{ + const struct browse_req *req = a; + const bdaddr_t *bdaddr = b; + + return bacmp(&req->bdaddr, bdaddr); +} + +static uint8_t browse_remote_sdp(const bdaddr_t *addr) +{ + struct browse_req *req; + uuid_t uuid; + + if (g_slist_find_custom(browse_reqs, addr, req_cmp)) + return HAL_STATUS_SUCCESS; + + req = g_new0(struct browse_req, 1); + bacpy(&req->bdaddr, addr); + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); + + if (bt_search_service(&adapter.bdaddr, + &req->bdaddr, &uuid, browse_cb, req, NULL , 0) < 0) { + browse_req_free(req); + return HAL_STATUS_FAILED; + } + + browse_reqs = g_slist_append(browse_reqs, req); + + return HAL_STATUS_SUCCESS; +} + +static void send_remote_sdp_rec_notify(bt_uuid_t *uuid, int channel, + char *name, uint8_t name_len, + uint8_t status, bdaddr_t *bdaddr) +{ + struct hal_prop_device_service_rec *prop; + uint8_t buf[BASELEN_REMOTE_DEV_PROP + name_len + sizeof(*prop)]; + struct hal_ev_remote_device_props *ev = (void *) buf; + size_t prop_len = sizeof(*prop) + name_len; + + memset(buf, 0, sizeof(buf)); + + if (uuid && status == HAL_STATUS_SUCCESS) { + prop = (void *) &ev->props[0].val; + prop->name_len = name_len; + prop->channel = (uint16_t)channel; + memcpy(prop->name, name, name_len); + memcpy(prop->uuid, &uuid->value.u128, sizeof(prop->uuid)); + } + + ev->num_props = 1; + ev->status = status; + ev->props[0].len = prop_len; + bdaddr2android(bdaddr, ev->bdaddr); + ev->props[0].type = HAL_PROP_DEVICE_SERVICE_REC; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_REMOTE_DEVICE_PROPS, + sizeof(buf), buf); +} + +static void find_remote_sdp_rec_cb(sdp_list_t *recs, int err, + gpointer user_data) +{ + bdaddr_t *addr = user_data; + uint8_t name_len; + uint8_t status; + char name_buf[256]; + int channel; + bt_uuid_t uuid; + uuid_t uuid128_sdp; + sdp_list_t *protos; + sdp_record_t *sdp_rec; + + if (err < 0) { + error("error while search remote sdp records"); + status = HAL_STATUS_FAILED; + send_remote_sdp_rec_notify(NULL, 0, NULL, 0, status, addr); + goto done; + } + + if (!recs) { + info("No service records found on remote"); + status = HAL_STATUS_SUCCESS; + send_remote_sdp_rec_notify(NULL, 0, NULL, 0, status, addr); + goto done; + } + + for ( ; recs; recs = recs->next) { + sdp_rec = recs->data; + + switch (sdp_rec->svclass.type) { + case SDP_UUID16: + sdp_uuid16_to_uuid128(&uuid128_sdp, + &sdp_rec->svclass); + break; + case SDP_UUID32: + sdp_uuid32_to_uuid128(&uuid128_sdp, + &sdp_rec->svclass); + break; + case SDP_UUID128: + break; + default: + error("wrong sdp uuid type"); + goto done; + } + + if (!sdp_get_access_protos(sdp_rec, &protos)) { + 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); + } else + channel = -1; + + if (channel < 0) { + error("can't get channel for sdp record"); + channel = 0; + } + + if (!sdp_get_service_name(sdp_rec, name_buf, sizeof(name_buf))) + name_len = strlen(name_buf); + else + name_len = 0; + + uuid.type = BT_UUID128; + memcpy(&uuid.value.u128, uuid128_sdp.value.uuid128.data, + sizeof(uuid.value.u128)); + status = HAL_STATUS_SUCCESS; + + send_remote_sdp_rec_notify(&uuid, channel, name_buf, name_len, + status, addr); + } + +done: + g_free(addr); +} + +static uint8_t find_remote_sdp_rec(const bdaddr_t *addr, + const uint8_t *find_uuid) +{ + bdaddr_t *bdaddr; + uuid_t uuid; + + /* from android we always get full 128bit length uuid */ + sdp_uuid128_create(&uuid, find_uuid); + + bdaddr = g_new(bdaddr_t, 1); + if (!bdaddr) + return HAL_STATUS_NOMEM; + + memcpy(bdaddr, addr, sizeof(*bdaddr)); + + if (bt_search_service(&adapter.bdaddr, addr, &uuid, + find_remote_sdp_rec_cb, bdaddr, NULL, 0) < 0) { + g_free(bdaddr); + return HAL_STATUS_FAILED; + } + + return HAL_STATUS_SUCCESS; +} + +static void new_link_key_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_link_key *ev = param; + const struct mgmt_addr_info *addr = &ev->key.addr; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small new link key event"); + return; + } + + ba2str(&addr->bdaddr, dst); + + DBG("new key for %s type %u pin_len %u", + dst, ev->key.type, ev->key.pin_len); + + if (ev->key.pin_len > 16) { + error("Invalid PIN length (%u) in new_key event", + ev->key.pin_len); + return; + } + + dev = get_device(&ev->key.addr.bdaddr, ev->key.addr.type); + if (!dev) + return; + + update_device_state(dev, ev->key.addr.type, HAL_STATUS_SUCCESS, false, + true, !!ev->store_hint); + + if (ev->store_hint) { + const struct mgmt_link_key_info *key = &ev->key; + + store_link_key(&addr->bdaddr, key->val, key->type, + key->pin_len); + } + + browse_remote_sdp(&addr->bdaddr); +} + +static uint8_t get_device_name(struct device *dev) +{ + send_device_property(dev, HAL_PROP_DEVICE_NAME, + strlen(dev->name), dev->name); + + return HAL_STATUS_SUCCESS; +} + +static void pin_code_request_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_pin_code_request *ev = param; + struct hal_ev_pin_request hal_ev; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small PIN code request event"); + return; + } + + ba2str(&ev->addr.bdaddr, dst); + + dev = get_device(&ev->addr.bdaddr, BDADDR_BREDR); + + /* + * Workaround for Android Bluetooth.apk issue: send remote + * device property + */ + get_device_name(dev); + + update_device_state(dev, ev->addr.type, HAL_STATUS_SUCCESS, true, + false, false); + + DBG("%s type %u secure %u", dst, ev->addr.type, ev->secure); + + /* Name already sent in remote device prop */ + memset(&hal_ev, 0, sizeof(hal_ev)); + get_device_android_addr(dev, hal_ev.bdaddr); + hal_ev.class_of_dev = dev->class; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_EV_PIN_REQUEST, + sizeof(hal_ev), &hal_ev); +} + +static void send_ssp_request(struct device *dev, uint8_t variant, + uint32_t passkey) +{ + struct hal_ev_ssp_request ev; + + memset(&ev, 0, sizeof(ev)); + + get_device_android_addr(dev, ev.bdaddr); + memcpy(ev.name, dev->name, strlen(dev->name)); + ev.class_of_dev = dev->class; + + ev.pairing_variant = variant; + ev.passkey = passkey; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_EV_SSP_REQUEST, + sizeof(ev), &ev); +} + +static void user_confirm_request_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_user_confirm_request *ev = param; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small user confirm request event"); + return; + } + + ba2str(&ev->addr.bdaddr, dst); + DBG("%s confirm_hint %u", dst, ev->confirm_hint); + + dev = get_device(&ev->addr.bdaddr, ev->addr.type); + if (!dev) + return; + + update_device_state(dev, ev->addr.type, HAL_STATUS_SUCCESS, true, + false, false); + + if (ev->confirm_hint) + send_ssp_request(dev, HAL_SSP_VARIANT_CONSENT, 0); + else + send_ssp_request(dev, HAL_SSP_VARIANT_CONFIRM, ev->value); +} + +static void user_passkey_request_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_user_passkey_request *ev = param; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small passkey request event"); + return; + } + + ba2str(&ev->addr.bdaddr, dst); + DBG("%s", dst); + + dev = get_device(&ev->addr.bdaddr, ev->addr.type); + if (!dev) + return; + + update_device_state(dev, ev->addr.type, HAL_STATUS_SUCCESS, true, + false, false); + + send_ssp_request(dev, HAL_SSP_VARIANT_ENTRY, 0); +} + +static void user_passkey_notify_callback(uint16_t index, uint16_t length, + const void *param, + void *user_data) +{ + const struct mgmt_ev_passkey_notify *ev = param; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small passkey notify event"); + return; + } + + ba2str(&ev->addr.bdaddr, dst); + DBG("%s entered %u", dst, ev->entered); + + /* HAL seems to not support entered characters */ + if (ev->entered) + return; + + dev = find_device(&ev->addr.bdaddr); + if (!dev) + return; + + update_device_state(dev, ev->addr.type, HAL_STATUS_SUCCESS, true, + false, false); + + send_ssp_request(dev, HAL_SSP_VARIANT_NOTIF, ev->passkey); +} + +static void clear_device_found(gpointer data, gpointer user_data) +{ + struct device *dev = data; + + dev->found = false; +} + +static uint8_t get_supported_discovery_type(void) +{ + uint8_t type = SCAN_TYPE_NONE; + + if (adapter.current_settings & MGMT_SETTING_BREDR) + type |= SCAN_TYPE_BREDR; + + if (adapter.current_settings & MGMT_SETTING_LE) + type |= SCAN_TYPE_LE; + + return type; +} + +static bool start_discovery(uint8_t type) +{ + struct mgmt_cp_start_discovery cp; + + cp.type = get_supported_discovery_type() & type; + + DBG("type=0x%x", cp.type); + + if (cp.type == SCAN_TYPE_NONE) + return false; + + if (mgmt_send(mgmt_if, MGMT_OP_START_DISCOVERY, adapter.index, + sizeof(cp), &cp, NULL, NULL, NULL) > 0) + return true; + + error("Failed to start discovery"); + + return false; +} + +/* + * Send discovery state change event only if it is related to dual type + * discovery session (triggered by start/cancel discovery commands) + */ +static void check_discovery_state(uint8_t new_type, uint8_t old_type) +{ + struct hal_ev_discovery_state_changed ev; + + DBG("%u %u", new_type, old_type); + + if (new_type == get_supported_discovery_type()) { + g_slist_foreach(bonded_devices, clear_device_found, NULL); + g_slist_foreach(cached_devices, clear_device_found, NULL); + ev.state = HAL_DISCOVERY_STATE_STARTED; + goto done; + } + + if (old_type != get_supported_discovery_type()) + return; + + ev.state = HAL_DISCOVERY_STATE_STOPPED; + +done: + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_DISCOVERY_STATE_CHANGED, sizeof(ev), &ev); +} + +static void mgmt_discovering_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_discovering *ev = param; + uint8_t type; + + if (length < sizeof(*ev)) { + error("Too small discovering event"); + return; + } + + DBG("type %u discovering %u", ev->type, ev->discovering); + + if (!!adapter.cur_discovery_type == !!ev->discovering) + return; + + type = ev->discovering ? ev->type : SCAN_TYPE_NONE; + + check_discovery_state(type, adapter.cur_discovery_type); + + adapter.cur_discovery_type = type; + + if (ev->discovering) { + adapter.exp_discovery_type = adapter.le_scanning ? + SCAN_TYPE_LE : SCAN_TYPE_NONE; + return; + } + + /* One shot notification about discovery stopped */ + if (gatt_discovery_stopped_cb) { + gatt_discovery_stopped_cb(); + gatt_discovery_stopped_cb = NULL; + } + + type = adapter.exp_discovery_type; + adapter.exp_discovery_type = adapter.le_scanning ? SCAN_TYPE_LE : + SCAN_TYPE_NONE; + + if (type != SCAN_TYPE_NONE) + start_discovery(type); +} + +static void confirm_device_name_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_confirm_name *rp = param; + struct device *dev; + + DBG("Confirm name status: %s (0x%02x)", mgmt_errstr(status), status); + + if (length < sizeof(*rp)) { + error("Wrong size of confirm name response"); + return; + } + + dev = find_device(&rp->addr.bdaddr); + if (!dev) + return; + + dev->confirm_id = 0; +} + +static unsigned int confirm_device_name(const bdaddr_t *addr, uint8_t addr_type, + bool resolve_name) +{ + struct mgmt_cp_confirm_name cp; + unsigned int res; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, addr); + cp.addr.type = addr_type; + + if (!resolve_name) + cp.name_known = 1; + + res = mgmt_send(mgmt_if, MGMT_OP_CONFIRM_NAME, adapter.index, + sizeof(cp), &cp, confirm_device_name_cb, + NULL, NULL); + if (!res) + error("Failed to send confirm name request"); + + return res; +} + +static int fill_hal_prop(void *buf, uint8_t type, uint16_t len, + const void *val) +{ + struct hal_property *prop = buf; + + prop->type = type; + prop->len = len; + + if (len) + memcpy(prop->val, val, len); + + return sizeof(*prop) + len; +} + +static uint8_t get_device_android_type(struct device *dev) +{ + if (dev->bredr && dev->le) + return HAL_TYPE_DUAL; + + if (dev->le) + return HAL_TYPE_LE; + + return HAL_TYPE_BREDR; +} + +uint8_t bt_get_device_android_type(const bdaddr_t *addr) +{ + struct device *dev; + + dev = get_device(addr, BDADDR_BREDR); + + return get_device_android_type(dev); +} + +bool bt_is_device_le(const bdaddr_t *addr) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return false; + + return dev->le; +} + +const bdaddr_t *bt_get_id_addr(const bdaddr_t *addr, uint8_t *type) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return NULL; + + if (type) + *type = dev->bdaddr_type; + + return &dev->bdaddr; +} + +const char *bt_get_adapter_name(void) +{ + return adapter.name; +} + +bool bt_device_is_bonded(const bdaddr_t *bdaddr) +{ + if (g_slist_find_custom(bonded_devices, bdaddr, device_match)) + return true; + + return false; +} + +bool bt_device_set_uuids(const bdaddr_t *addr, GSList *uuids) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return false; + + set_device_uuids(dev, uuids); + + return true; +} + +bool bt_kernel_conn_control(void) +{ + return kernel_conn_control; +} + +bool bt_auto_connect_add(const bdaddr_t *addr) +{ + struct mgmt_cp_add_device cp; + struct device *dev; + + if (!kernel_conn_control) + return false; + + dev = find_device(addr); + if (!dev) + return false; + + if (dev->bdaddr_type == BDADDR_BREDR) { + DBG("auto-connection feature is not available for BR/EDR"); + return false; + } + + if (dev->in_white_list) { + DBG("Device already in white list"); + return true; + } + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, addr); + cp.addr.type = dev->bdaddr_type; + cp.action = 0x02; + + if (mgmt_send(mgmt_if, MGMT_OP_ADD_DEVICE, adapter.index, sizeof(cp), + &cp, NULL, NULL, NULL) > 0) { + dev->in_white_list = true; + return true; + } + + error("Failed to add device"); + + return false; +} + +void bt_auto_connect_remove(const bdaddr_t *addr) +{ + struct mgmt_cp_remove_device cp; + struct device *dev; + + if (!kernel_conn_control) + return; + + dev = find_device(addr); + if (!dev) + return; + + if (dev->bdaddr_type == BDADDR_BREDR) { + DBG("auto-connection feature is not available for BR/EDR"); + return; + } + + if (!dev->in_white_list) { + DBG("Device already removed from white list"); + return; + } + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, addr); + cp.addr.type = dev->bdaddr_type; + + if (mgmt_send(mgmt_if, MGMT_OP_REMOVE_DEVICE, adapter.index, + sizeof(cp), &cp, NULL, NULL, NULL) > 0) { + dev->in_white_list = false; + return; + } + + error("Failed to remove device"); +} + +static bool match_by_value(const void *data, const void *user_data) +{ + return data == user_data; +} + +bool bt_unpaired_register(bt_unpaired_device_cb cb) +{ + if (queue_find(unpaired_cb_list, match_by_value, cb)) + return false; + + return queue_push_head(unpaired_cb_list, cb); +} + +void bt_unpaired_unregister(bt_unpaired_device_cb cb) +{ + queue_remove(unpaired_cb_list, cb); +} + +bool bt_paired_register(bt_paired_device_cb cb) +{ + if (queue_find(paired_cb_list, match_by_value, cb)) + return false; + + return queue_push_head(paired_cb_list, cb); +} + +void bt_paired_unregister(bt_paired_device_cb cb) +{ + queue_remove(paired_cb_list, cb); +} + +bool bt_is_pairing(const bdaddr_t *addr) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return false; + + return dev->pairing; +} + +static bool rssi_above_threshold(int old, int new) +{ + /* only 8 dBm or more */ + return abs(old - new) >= 8; +} + +static void update_new_device(struct device *dev, int8_t rssi, + const struct eir_data *eir) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_device_found *ev = (void *) buf; + uint8_t android_bdaddr[6]; + uint8_t android_type; + size_t size; + + memset(buf, 0, sizeof(buf)); + + if (adapter.cur_discovery_type) + dev->found = true; + + size = sizeof(*ev); + + get_device_android_addr(dev, android_bdaddr); + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_ADDR, + sizeof(android_bdaddr), android_bdaddr); + ev->num_props++; + + android_type = get_device_android_type(dev); + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_TYPE, + sizeof(android_type), &android_type); + ev->num_props++; + + if (eir->class) + dev->class = eir->class; + + if (dev->class) { + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_CLASS, + sizeof(dev->class), &dev->class); + ev->num_props++; + } + + if (rssi && rssi_above_threshold(dev->rssi, rssi)) + dev->rssi = rssi; + + if (dev->rssi) { + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_RSSI, + sizeof(dev->rssi), &dev->rssi); + ev->num_props++; + } + + if (eir->name && strlen(eir->name)) { + g_free(dev->name); + dev->name = g_strdup(eir->name); + } + + if (dev->name) { + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_NAME, + strlen(dev->name), dev->name); + ev->num_props++; + + /* when updating name also send stored friendly name */ + if (dev->friendly_name) { + size += fill_hal_prop(buf + size, + HAL_PROP_DEVICE_FRIENDLY_NAME, + strlen(dev->friendly_name), + dev->friendly_name); + ev->num_props++; + } + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_EV_DEVICE_FOUND, + size, buf); +} + +static void update_device(struct device *dev, int8_t rssi, + const struct eir_data *eir) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_remote_device_props *ev = (void *) buf; + uint8_t old_type, new_type; + size_t size; + + memset(buf, 0, sizeof(buf)); + + size = sizeof(*ev); + + ev->status = HAL_STATUS_SUCCESS; + get_device_android_addr(dev, ev->bdaddr); + + old_type = get_device_android_type(dev); + + new_type = get_device_android_type(dev); + + if (old_type != new_type) { + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_TYPE, + sizeof(new_type), &new_type); + ev->num_props++; + } + + if (eir->class && dev->class != eir->class) { + dev->class = eir->class; + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_CLASS, + sizeof(dev->class), &dev->class); + ev->num_props++; + } + + if (rssi && rssi_above_threshold(dev->rssi, rssi)) { + dev->rssi = rssi; + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_RSSI, + sizeof(dev->rssi), &dev->rssi); + ev->num_props++; + } + + if (eir->name && strlen(eir->name) && strcmp(dev->name, eir->name)) { + g_free(dev->name); + dev->name = g_strdup(eir->name); + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_NAME, + strlen(dev->name), dev->name); + ev->num_props++; + + /* when updating name also send stored friendly name */ + if (dev->friendly_name) { + size += fill_hal_prop(buf + size, + HAL_PROP_DEVICE_FRIENDLY_NAME, + strlen(dev->friendly_name), + dev->friendly_name); + ev->num_props++; + } + } + + if (ev->num_props) + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_REMOTE_DEVICE_PROPS, size, buf); +} + +static bool is_new_device(const struct device *dev, unsigned int flags, + uint8_t bdaddr_type) +{ + if (dev->found) + return false; + + if (dev->bredr_paired || dev->le_paired) + return false; + + if (bdaddr_type != BDADDR_BREDR && + !(flags & (EIR_LIM_DISC | EIR_GEN_DISC))) + return false; + + return true; +} + +static void update_found_device(const bdaddr_t *bdaddr, uint8_t bdaddr_type, + int8_t rssi, bool confirm, + bool connectable, + const uint8_t *data, uint8_t data_len) +{ + struct eir_data eir; + struct device *dev; + + memset(&eir, 0, sizeof(eir)); + + eir_parse(&eir, data, data_len); + + dev = get_device(bdaddr, bdaddr_type); + + if (bdaddr_type == BDADDR_BREDR) { + dev->bredr = true; + dev->bredr_seen = time(NULL); + } else { + dev->le = true; + dev->bdaddr_type = bdaddr_type; + dev->le_seen = time(NULL); + } + + /* + * Device found event needs to be send also for known device if this is + * new discovery session. Otherwise framework will ignore it. + */ + if (is_new_device(dev, eir.flags, bdaddr_type)) + update_new_device(dev, rssi, &eir); + else + update_device(dev, rssi, &eir); + + eir_data_free(&eir); + + /* Notify Gatt if its registered for LE events */ + if (bdaddr_type != BDADDR_BREDR && gatt_device_found_cb) { + const bdaddr_t *addr; + + /* + * If RPA is set it means that IRK was received and ID address + * is being used. Android Framework is still using old RPA and + * it needs to be used also in GATT notifications. Also GATT + * HAL implementation is using RPA for devices matching. + */ + if (bacmp(&dev->rpa, BDADDR_ANY)) + addr = &dev->rpa; + else + addr = &dev->bdaddr; + + gatt_device_found_cb(addr, rssi, data_len, data, connectable, + dev->le_bonded); + } + + if (!dev->bredr_paired && !dev->le_paired) + cache_device(dev); + + if (confirm) { + char addr[18]; + bool resolve_name = true; + + ba2str(bdaddr, addr); + + /* + * Don't need to confirm name if we have it already in cache + * Just check if device name is different than bdaddr + */ + if (g_strcmp0(dev->name, addr)) { + get_device_name(dev); + resolve_name = false; + } + + info("Device %s needs name confirmation (resolve_name=%d)", + addr, resolve_name); + dev->confirm_id = confirm_device_name(bdaddr, bdaddr_type, + resolve_name); + } +} + +static void mgmt_device_found_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_found *ev = param; + const uint8_t *eir; + uint16_t eir_len; + uint32_t flags; + bool confirm_name; + bool connectable; + char addr[18]; + + if (length < sizeof(*ev)) { + error("Too short device found event (%u bytes)", length); + return; + } + + eir_len = le16_to_cpu(ev->eir_len); + if (length != sizeof(*ev) + eir_len) { + error("Device found event size mismatch (%u != %zu)", + length, sizeof(*ev) + eir_len); + return; + } + + if (eir_len == 0) + eir = NULL; + else + eir = ev->eir; + + flags = le32_to_cpu(ev->flags); + + ba2str(&ev->addr.bdaddr, addr); + DBG("hci%u addr %s, rssi %d flags 0x%04x eir_len %u", + index, addr, ev->rssi, flags, eir_len); + + confirm_name = flags & MGMT_DEV_FOUND_CONFIRM_NAME; + connectable = !(flags & MGMT_DEV_FOUND_NOT_CONNECTABLE); + + update_found_device(&ev->addr.bdaddr, ev->addr.type, ev->rssi, + confirm_name, connectable, eir, eir_len); +} + +static void mgmt_device_connected_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_connected *ev = param; + struct hal_ev_acl_state_changed hal_ev; + struct device *dev; + char addr[18]; + + if (length < sizeof(*ev)) { + error("Too short device connected event (%u bytes)", length); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + DBG("%s type %u", addr, ev->addr.type); + + update_found_device(&ev->addr.bdaddr, ev->addr.type, 0, false, false, + &ev->eir[0], le16_to_cpu(ev->eir_len)); + + hal_ev.status = HAL_STATUS_SUCCESS; + hal_ev.state = HAL_ACL_STATE_CONNECTED; + + dev = find_device(&ev->addr.bdaddr); + if (!dev) + return; + + dev->connected = true; + + get_device_android_addr(dev, hal_ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_ACL_STATE_CHANGED, sizeof(hal_ev), &hal_ev); +} + +static bool device_is_paired(struct device *dev, uint8_t addr_type) +{ + if (addr_type == BDADDR_BREDR) + return dev->bredr_paired; + + return dev->le_paired; +} + +static bool device_is_bonded(struct device *dev) +{ + return dev->bredr_bonded || dev->le_bonded; +} + +static void mgmt_device_disconnected_event(uint16_t index, uint16_t length, + const void *param, + void *user_data) +{ + const struct mgmt_ev_device_disconnected *ev = param; + struct hal_ev_acl_state_changed hal_ev; + struct device *dev; + uint8_t type = ev->addr.type; + + if (length < sizeof(*ev)) { + error("Too short device disconnected event (%u bytes)", length); + return; + } + + dev = find_device(&ev->addr.bdaddr); + if (!dev) + return; + + hal_ev.status = HAL_STATUS_SUCCESS; + hal_ev.state = HAL_ACL_STATE_DISCONNECTED; + get_device_android_addr(dev, hal_ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_ACL_STATE_CHANGED, sizeof(hal_ev), &hal_ev); + + if (device_is_paired(dev, type) && !device_is_bonded(dev)) + update_device_state(dev, type, HAL_STATUS_SUCCESS, false, + false, false); + + dev->connected = false; +} + +static uint8_t status_mgmt2hal(uint8_t mgmt) +{ + switch (mgmt) { + case MGMT_STATUS_SUCCESS: + return HAL_STATUS_SUCCESS; + case MGMT_STATUS_NO_RESOURCES: + return HAL_STATUS_NOMEM; + case MGMT_STATUS_BUSY: + return HAL_STATUS_BUSY; + case MGMT_STATUS_NOT_SUPPORTED: + return HAL_STATUS_UNSUPPORTED; + case MGMT_STATUS_INVALID_PARAMS: + return HAL_STATUS_INVALID; + case MGMT_STATUS_AUTH_FAILED: + return HAL_STATUS_AUTH_FAILURE; + case MGMT_STATUS_NOT_CONNECTED: + return HAL_STATUS_REMOTE_DEVICE_DOWN; + default: + return HAL_STATUS_FAILED; + } +} + +static void mgmt_connect_failed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_connect_failed *ev = param; + struct device *dev; + + if (length < sizeof(*ev)) { + error("Too short connect failed event (%u bytes)", length); + return; + } + + DBG(""); + + dev = find_device(&ev->addr.bdaddr); + if (!dev) + return; + + /* + * In case security mode 3 pairing we will get connect failed event + * in case e.g wrong PIN code entered. Let's check if device is + * bonding, if so update bond state + */ + + if (!dev->pairing) + return; + + update_device_state(dev, ev->addr.type, status_mgmt2hal(ev->status), + false, false, false); +} + +static void mgmt_auth_failed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_auth_failed *ev = param; + struct device *dev; + + if (length < sizeof(*ev)) { + error("Too small auth failed mgmt event (%u bytes)", length); + return; + } + + DBG(""); + + dev = find_device(&ev->addr.bdaddr); + if (!dev) + return; + + if (!dev->pairing) + return; + + update_device_state(dev, ev->addr.type, status_mgmt2hal(ev->status), + false, false, false); +} + +static void mgmt_device_unpaired_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_unpaired *ev = param; + struct device *dev; + + if (length < sizeof(*ev)) { + error("Too small device unpaired event (%u bytes)", length); + return; + } + + DBG(""); + + /* TODO should device be disconnected ? */ + + dev = find_device(&ev->addr.bdaddr); + if (!dev) + return; + + update_device_state(dev, ev->addr.type, HAL_STATUS_SUCCESS, false, + false, false); + + /* Unpaired device is removed from the white list */ + dev->in_white_list = false; +} + +static void store_ltk(const bdaddr_t *dst, uint8_t bdaddr_type, bool master, + const uint8_t *key, uint8_t key_type, uint8_t enc_size, + uint16_t ediv, uint64_t rand) +{ + const char *key_s, *keytype_s, *encsize_s, *ediv_s, *rand_s; + GKeyFile *key_file; + char key_str[33]; + gsize length = 0; + char addr[18]; + char *data; + int i; + + key_file = g_key_file_new(); + if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) { + g_key_file_free(key_file); + return; + } + + ba2str(dst, addr); + + key_s = master ? "LongTermKey" : "SlaveLongTermKey"; + keytype_s = master ? "LongTermKeyType" : "SlaveLongTermKeyType"; + encsize_s = master ? "LongTermKeyEncSize" : "SlaveLongTermKeyEncSize"; + ediv_s = master ? "LongTermKeyEDiv" : "SlaveLongTermKeyEDiv"; + rand_s = master ? "LongTermKeyRand" : "SlaveLongTermKeyRand"; + + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", key[i]); + + g_key_file_set_string(key_file, addr, key_s, key_str); + + g_key_file_set_integer(key_file, addr, keytype_s, key_type); + + g_key_file_set_integer(key_file, addr, encsize_s, enc_size); + + g_key_file_set_integer(key_file, addr, ediv_s, ediv); + + g_key_file_set_uint64(key_file, addr, rand_s, rand); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(DEVICES_FILE, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +static void new_long_term_key_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_long_term_key *ev = param; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small long term key event (%u bytes)", length); + return; + } + + ba2str(&ev->key.addr.bdaddr, dst); + + DBG("new LTK for %s type %u enc_size %u store_hint %u", + dst, ev->key.type, ev->key.enc_size, ev->store_hint); + + dev = find_device(&ev->key.addr.bdaddr); + if (!dev) + return; + + update_device_state(dev, ev->key.addr.type, HAL_STATUS_SUCCESS, false, + true, !!ev->store_hint); + + if (ev->store_hint) { + const struct mgmt_ltk_info *key = &ev->key; + uint16_t ediv; + uint64_t rand; + + ediv = le16_to_cpu(key->ediv); + rand = le64_to_cpu(key->rand); + + store_ltk(&key->addr.bdaddr, key->addr.type, key->master, + key->val, key->type, key->enc_size, ediv, rand); + } + + /* TODO browse services here? */ +} + +static void store_csrk(struct device *dev) +{ + GKeyFile *key_file; + char key_str[33]; + char addr[18]; + int i; + gsize length = 0; + char *data; + + ba2str(&dev->bdaddr, addr); + + key_file = g_key_file_new(); + if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) { + g_key_file_free(key_file); + return; + } + + if (dev->valid_local_csrk) { + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", + dev->local_csrk[i]); + + g_key_file_set_string(key_file, addr, "LocalCSRK", key_str); + + g_key_file_set_boolean(key_file, addr, "LocalCSRKAuthenticated", + dev->local_csrk_auth); + } + + if (dev->valid_remote_csrk) { + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", + dev->remote_csrk[i]); + + g_key_file_set_string(key_file, addr, "RemoteCSRK", key_str); + + g_key_file_set_boolean(key_file, addr, + "RemoteCSRKAuthenticated", + dev->remote_csrk_auth); + } + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(DEVICES_FILE, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +static void new_csrk_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_csrk *ev = param; + struct device *dev; + char dst[18]; + + if (length < sizeof(*ev)) { + error("Too small csrk event (%u bytes)", length); + return; + } + + ba2str(&ev->key.addr.bdaddr, dst); + dev = get_device(&ev->key.addr.bdaddr, ev->key.addr.type); + if (!dev) + return; + + switch (ev->key.type) { + case 0x00: + case 0x02: + memcpy(dev->local_csrk, ev->key.val, 16); + dev->local_sign_cnt = 0; + dev->valid_local_csrk = true; + dev->local_csrk_auth = ev->key.type == 0x02; + break; + case 0x01: + case 0x03: + memcpy(dev->remote_csrk, ev->key.val, 16); + dev->remote_sign_cnt = 0; + dev->valid_remote_csrk = true; + dev->remote_csrk_auth = ev->key.type == 0x03; + break; + default: + error("Unknown CSRK key type 02%02x", ev->key.type); + return; + } + + update_device_state(dev, ev->key.addr.type, HAL_STATUS_SUCCESS, false, + true, !!ev->store_hint); + + if (ev->store_hint) + store_csrk(dev); +} + +static void store_irk(struct device *dev, const uint8_t *val) +{ + GKeyFile *key_file; + char key_str[33]; + char addr[18]; + int i; + gsize length = 0; + char *data; + + ba2str(&dev->bdaddr, addr); + + key_file = g_key_file_new(); + if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) { + g_key_file_free(key_file); + return; + } + + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", val[i]); + + g_key_file_set_string(key_file, addr, "IdentityResolvingKey", key_str); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(DEVICES_FILE, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +static void new_irk_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_irk *ev = param; + const struct mgmt_addr_info *addr = &ev->key.addr; + struct device *dev; + char dst[18], rpa[18]; + + if (length < sizeof(*ev)) { + error("To small New Irk Event (%u bytes)", length); + return; + } + + ba2str(&ev->key.addr.bdaddr, dst); + ba2str(&ev->rpa, rpa); + + DBG("new IRK for %s, RPA %s", dst, rpa); + + if (!bacmp(&ev->rpa, BDADDR_ANY)) { + dev = get_device(&addr->bdaddr, addr->type); + if (!dev) + return; + } else { + dev = find_device(&addr->bdaddr); + + if (dev && dev->bredr_paired) { + bacpy(&dev->rpa, &addr->bdaddr); + dev->rpa_type = addr->type; + + /* TODO merge properties ie. UUIDs */ + } else { + dev = find_device(&ev->rpa); + if (!dev) + return; + + /* don't leave garbage in cache file */ + remove_device_info(dev, CACHE_FILE); + + /* + * RPA resolution is transparent for Android Framework + * ie. device is still access by RPA so it need to be + * keep. After bluetoothd restart device is advertised + * to Android with IDA and RPA is not set. + */ + bacpy(&dev->rpa, &dev->bdaddr); + dev->rpa_type = dev->bdaddr_type; + + bacpy(&dev->bdaddr, &addr->bdaddr); + dev->bdaddr_type = addr->type; + } + } + + update_device_state(dev, ev->key.addr.type, HAL_STATUS_SUCCESS, false, + true, !!ev->store_hint); + + if (ev->store_hint) + store_irk(dev, ev->key.val); +} + +static void register_mgmt_handlers(void) +{ + mgmt_register(mgmt_if, MGMT_EV_NEW_SETTINGS, adapter.index, + new_settings_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_CLASS_OF_DEV_CHANGED, adapter.index, + mgmt_dev_class_changed_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_LOCAL_NAME_CHANGED, adapter.index, + mgmt_local_name_changed_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_NEW_LINK_KEY, adapter.index, + new_link_key_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_PIN_CODE_REQUEST, adapter.index, + pin_code_request_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_USER_CONFIRM_REQUEST, adapter.index, + user_confirm_request_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_USER_PASSKEY_REQUEST, adapter.index, + user_passkey_request_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_PASSKEY_NOTIFY, adapter.index, + user_passkey_notify_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_DISCOVERING, adapter.index, + mgmt_discovering_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_DEVICE_FOUND, adapter.index, + mgmt_device_found_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_DEVICE_CONNECTED, adapter.index, + mgmt_device_connected_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_DEVICE_DISCONNECTED, adapter.index, + mgmt_device_disconnected_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_CONNECT_FAILED, adapter.index, + mgmt_connect_failed_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_AUTH_FAILED, adapter.index, + mgmt_auth_failed_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_DEVICE_UNPAIRED, adapter.index, + mgmt_device_unpaired_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_NEW_LONG_TERM_KEY, adapter.index, + new_long_term_key_event, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_NEW_CSRK, adapter.index, + new_csrk_callback, NULL, NULL); + + mgmt_register(mgmt_if, MGMT_EV_NEW_IRK, adapter.index, new_irk_callback, + NULL, NULL); +} + +static void load_link_keys_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + bt_bluetooth_ready cb = user_data; + int err; + + if (status) { + error("Failed to load link keys for index %u: %s (0x%02x)", + adapter.index, mgmt_errstr(status), status); + err = -EIO; + goto failed; + } + + DBG("status %u", status); + + cb(0, &adapter.bdaddr); + return; + +failed: + cb(err, NULL); +} + +static void load_link_keys(GSList *keys, bt_bluetooth_ready cb) +{ + struct mgmt_cp_load_link_keys *cp; + struct mgmt_link_key_info *key; + size_t key_count, cp_size; + unsigned int id; + + key_count = g_slist_length(keys); + + DBG("keys %zu ", key_count); + + cp_size = sizeof(*cp) + (key_count * sizeof(*key)); + + cp = g_malloc0(cp_size); + + /* + * Even if the list of stored keys is empty, it is important to + * load an empty list into the kernel. That way it is ensured + * that no old keys from a previous daemon are present. + */ + cp->key_count = cpu_to_le16(key_count); + + for (key = cp->keys; keys != NULL; keys = g_slist_next(keys), key++) + memcpy(key, keys->data, sizeof(*key)); + + id = mgmt_send(mgmt_if, MGMT_OP_LOAD_LINK_KEYS, adapter.index, + cp_size, cp, load_link_keys_complete, cb, NULL); + + g_free(cp); + + if (id == 0) { + error("Failed to load link keys"); + cb(-EIO, NULL); + } +} + +static void load_ltk_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + if (status == MGMT_STATUS_SUCCESS) + return; + + info("Failed to load LTKs: %s (0x%02x)", mgmt_errstr(status), status); +} + +static void load_ltks(GSList *ltks) +{ + struct mgmt_cp_load_long_term_keys *cp; + struct mgmt_ltk_info *ltk; + size_t ltk_count, cp_size; + GSList *l; + + ltk_count = g_slist_length(ltks); + + DBG("ltks %zu", ltk_count); + + cp_size = sizeof(*cp) + (ltk_count * sizeof(*ltk)); + + cp = g_malloc0(cp_size); + + /* + * Even if the list of stored keys is empty, it is important to load + * an empty list into the kernel. That way it is ensured that no old + * keys from a previous daemon are present. + */ + cp->key_count = cpu_to_le16(ltk_count); + + for (l = ltks, ltk = cp->keys; l != NULL; l = g_slist_next(l), ltk++) + memcpy(ltk, l->data, sizeof(*ltk)); + + if (mgmt_send(mgmt_if, MGMT_OP_LOAD_LONG_TERM_KEYS, adapter.index, + cp_size, cp, load_ltk_complete, NULL, NULL) == 0) + error("Failed to load LTKs"); + + g_free(cp); +} + +static void load_irk_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + if (status == MGMT_STATUS_SUCCESS) + return; + + info("Failed to load IRKs: %s (0x%02x)", mgmt_errstr(status), status); +} + +static void load_irks(GSList *irks) +{ + struct mgmt_cp_load_irks *cp; + struct mgmt_irk_info *irk; + size_t irk_count, cp_size; + GSList *l; + + irk_count = g_slist_length(irks); + + DBG("irks %zu", irk_count); + + cp_size = sizeof(*cp) + (irk_count * sizeof(*irk)); + + cp = g_malloc0(cp_size); + + cp->irk_count = cpu_to_le16(irk_count); + + for (l = irks, irk = cp->irks; l != NULL; l = g_slist_next(l), irk++) + memcpy(irk, irks->data, sizeof(*irk)); + + if (mgmt_send(mgmt_if, MGMT_OP_LOAD_IRKS, adapter.index, cp_size, cp, + load_irk_complete, NULL, NULL) == 0) + error("Failed to load IRKs"); + + g_free(cp); +} + +static uint8_t get_adapter_uuids(void) +{ + struct hal_ev_adapter_props_changed *ev; + GSList *list = adapter.uuids; + size_t uuid_count = g_slist_length(list); + size_t len = uuid_count * sizeof(uint128_t); + uint8_t buf[BASELEN_PROP_CHANGED + len]; + uint8_t *p; + + memset(buf, 0, sizeof(buf)); + ev = (void *) buf; + + ev->num_props = 1; + ev->status = HAL_STATUS_SUCCESS; + + ev->props[0].type = HAL_PROP_ADAPTER_UUIDS; + ev->props[0].len = len; + p = ev->props->val; + + for (; list; list = g_slist_next(list)) { + uuid_t *uuid = list->data; + + memcpy(p, &uuid->value.uuid128, sizeof(uint128_t)); + + p += sizeof(uint128_t); + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_ADAPTER_PROPS_CHANGED, sizeof(buf), ev); + + return HAL_STATUS_SUCCESS; +} + +static void remove_uuid_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to remove UUID: %s (0x%02x)", mgmt_errstr(status), + status); + return; + } + + mgmt_dev_class_changed_event(adapter.index, length, param, NULL); + + get_adapter_uuids(); +} + +static void remove_uuid(uuid_t *uuid) +{ + uint128_t uint128; + struct mgmt_cp_remove_uuid cp; + + ntoh128((uint128_t *) uuid->value.uuid128.data, &uint128); + htob128(&uint128, (uint128_t *) cp.uuid); + + mgmt_send(mgmt_if, MGMT_OP_REMOVE_UUID, adapter.index, sizeof(cp), &cp, + remove_uuid_complete, NULL, NULL); +} + +static void add_uuid_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to add UUID: %s (0x%02x)", mgmt_errstr(status), + status); + return; + } + + mgmt_dev_class_changed_event(adapter.index, length, param, NULL); + + get_adapter_uuids(); +} + +static void add_uuid(uint8_t svc_hint, uuid_t *uuid) +{ + uint128_t uint128; + struct mgmt_cp_add_uuid cp; + + ntoh128((uint128_t *) uuid->value.uuid128.data, &uint128); + htob128(&uint128, (uint128_t *) cp.uuid); + + cp.svc_hint = svc_hint; + + mgmt_send(mgmt_if, MGMT_OP_ADD_UUID, adapter.index, sizeof(cp), &cp, + add_uuid_complete, NULL, NULL); +} + +int bt_adapter_add_record(sdp_record_t *rec, uint8_t svc_hint) +{ + uuid_t *uuid; + + uuid = sdp_uuid_to_uuid128(&rec->svclass); + + if (g_slist_find_custom(adapter.uuids, uuid, sdp_uuid_cmp)) { + char uuid_str[32]; + + sdp_uuid2strn(uuid, uuid_str, sizeof(uuid_str)); + DBG("UUID %s already added", uuid_str); + + bt_free(uuid); + return -EALREADY; + } + + adapter.uuids = g_slist_prepend(adapter.uuids, uuid); + + add_uuid(svc_hint, uuid); + + return add_record_to_server(&adapter.bdaddr, rec); +} + +void bt_adapter_remove_record(uint32_t handle) +{ + sdp_record_t *rec; + GSList *uuid_found; + + rec = sdp_record_find(handle); + if (!rec) + return; + + uuid_found = g_slist_find_custom(adapter.uuids, &rec->svclass, + sdp_uuid_cmp); + if (uuid_found) { + uuid_t *uuid = uuid_found->data; + + remove_uuid(uuid); + + adapter.uuids = g_slist_remove(adapter.uuids, uuid); + + free(uuid); + } + + remove_record_from_server(handle); +} + +static void set_mode_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to set mode: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + new_settings_callback(adapter.index, length, param, NULL); +} + +static bool set_mode(uint16_t opcode, uint8_t mode) +{ + struct mgmt_mode cp; + + memset(&cp, 0, sizeof(cp)); + cp.val = mode; + + DBG("opcode=0x%x mode=0x%x", opcode, mode); + + if (mgmt_send(mgmt_if, opcode, adapter.index, sizeof(cp), &cp, + set_mode_complete, NULL, NULL) > 0) + return true; + + error("Failed to set mode"); + + return false; +} + +static void set_io_capability(void) +{ + struct mgmt_cp_set_io_capability cp; + + memset(&cp, 0, sizeof(cp)); + cp.io_capability = DEFAULT_IO_CAPABILITY; + + if (mgmt_send(mgmt_if, MGMT_OP_SET_IO_CAPABILITY, adapter.index, + sizeof(cp), &cp, NULL, NULL, NULL) == 0) + error("Failed to set IO capability"); +} + +static void set_device_id(void) +{ + struct mgmt_cp_set_device_id cp; + + memset(&cp, 0, sizeof(cp)); + cp.source = cpu_to_le16(bt_config_get_pnp_source()); + cp.vendor = cpu_to_le16(bt_config_get_pnp_vendor()); + cp.product = cpu_to_le16(bt_config_get_pnp_product()); + cp.version = cpu_to_le16(bt_config_get_pnp_version()); + + if (mgmt_send(mgmt_if, MGMT_OP_SET_DEVICE_ID, adapter.index, + sizeof(cp), &cp, NULL, NULL, NULL) == 0) + error("Failed to set device id"); + + register_device_id(bt_config_get_pnp_source(), + bt_config_get_pnp_vendor(), + bt_config_get_pnp_product(), + bt_config_get_pnp_version()); + + bt_adapter_add_record(sdp_record_find(0x10000), 0x00); +} + +static void set_adapter_name_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_cp_set_local_name *rp = param; + + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to set name: %s (0x%02x)", mgmt_errstr(status), + status); + return; + } + + adapter_set_name(rp->name); +} + +static uint8_t set_adapter_name(const uint8_t *name, uint16_t len) +{ + struct mgmt_cp_set_local_name cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(cp.name, name, len); + + if (mgmt_send(mgmt_if, MGMT_OP_SET_LOCAL_NAME, adapter.index, + sizeof(cp), &cp, set_adapter_name_complete, + NULL, NULL) > 0) + return HAL_STATUS_SUCCESS; + + error("Failed to set name"); + + return HAL_STATUS_FAILED; +} + +static uint8_t set_adapter_discoverable_timeout(const void *buf, uint16_t len) +{ + const uint32_t *timeout = buf; + + if (len != sizeof(*timeout)) { + error("Invalid set disc timeout size (%u bytes), terminating", + len); + raise(SIGTERM); + return HAL_STATUS_FAILED; + } + + /* + * Android handles discoverable timeout in Settings app. + * There is no need to use kernel feature for that. + * Just need to store this value here + */ + + memcpy(&adapter.discoverable_timeout, timeout, sizeof(uint32_t)); + + store_adapter_config(); + + send_adapter_property(HAL_PROP_ADAPTER_DISC_TIMEOUT, + sizeof(adapter.discoverable_timeout), + &adapter.discoverable_timeout); + + return HAL_STATUS_SUCCESS; +} + +static void clear_uuids(void) +{ + struct mgmt_cp_remove_uuid cp; + + memset(&cp, 0, sizeof(cp)); + + mgmt_send(mgmt_if, MGMT_OP_REMOVE_UUID, adapter.index, sizeof(cp), + &cp, NULL, NULL, NULL); +} + +static struct device *create_device_from_info(GKeyFile *key_file, + const char *peer) +{ + struct device *dev; + uint8_t type; + bdaddr_t bdaddr; + char **uuids; + char *str; + + /* BREDR if not present */ + type = g_key_file_get_integer(key_file, peer, "AddressType", NULL); + + str2ba(peer, &bdaddr); + dev = create_device(&bdaddr, type); + + if (type != BDADDR_BREDR) + dev->bredr = g_key_file_get_boolean(key_file, peer, "BREDR", + NULL); + + str = g_key_file_get_string(key_file, peer, "LocalCSRK", NULL); + if (str) { + int i; + + dev->valid_local_csrk = true; + for (i = 0; i < 16; i++) + sscanf(str + (i * 2), "%02hhX", &dev->local_csrk[i]); + + g_free(str); + + dev->local_sign_cnt = g_key_file_get_integer(key_file, peer, + "LocalCSRKSignCounter", NULL); + + dev->local_csrk_auth = g_key_file_get_boolean(key_file, peer, + "LocalCSRKAuthenticated", NULL); + } + + str = g_key_file_get_string(key_file, peer, "RemoteCSRK", NULL); + if (str) { + int i; + + dev->valid_remote_csrk = true; + for (i = 0; i < 16; i++) + sscanf(str + (i * 2), "%02hhX", &dev->remote_csrk[i]); + + g_free(str); + + dev->remote_sign_cnt = g_key_file_get_integer(key_file, peer, + "RemoteCSRKSignCounter", NULL); + + dev->remote_csrk_auth = g_key_file_get_boolean(key_file, peer, + "RemoteCSRKAuthenticated", + NULL); + } + + str = g_key_file_get_string(key_file, peer, "GattCCC", NULL); + if (str) { + dev->gatt_ccc = atoi(str); + g_free(str); + } + + str = g_key_file_get_string(key_file, peer, "Name", NULL); + if (str) { + g_free(dev->name); + dev->name = str; + } + + str = g_key_file_get_string(key_file, peer, "FriendlyName", NULL); + if (str) { + g_free(dev->friendly_name); + dev->friendly_name = str; + } + + dev->class = g_key_file_get_integer(key_file, peer, "Class", NULL); + + if (dev->bredr) + dev->bredr_seen = g_key_file_get_integer(key_file, peer, + "Timestamp", + NULL); + else + dev->le_seen = g_key_file_get_integer(key_file, peer, + "Timestamp", NULL); + + uuids = g_key_file_get_string_list(key_file, peer, "Services", NULL, + NULL); + if (uuids) { + char **uuid; + + for (uuid = uuids; *uuid; uuid++) { + uint8_t *u = g_malloc0(16); + int i; + + for (i = 0; i < 16; i++) + sscanf((*uuid) + (i * 2), "%02hhX", &u[i]); + + dev->uuids = g_slist_append(dev->uuids, u); + } + + g_strfreev(uuids); + } + + return dev; +} + +static struct mgmt_link_key_info *get_key_info(GKeyFile *key_file, + const char *peer) +{ + struct mgmt_link_key_info *info = NULL; + char *str; + unsigned int i; + + str = g_key_file_get_string(key_file, peer, "LinkKey", NULL); + if (!str || strlen(str) != 32) + goto failed; + + info = g_new0(struct mgmt_link_key_info, 1); + + str2ba(peer, &info->addr.bdaddr); + + for (i = 0; i < sizeof(info->val); i++) + sscanf(str + (i * 2), "%02hhX", &info->val[i]); + + info->type = g_key_file_get_integer(key_file, peer, "LinkKeyType", + NULL); + info->pin_len = g_key_file_get_integer(key_file, peer, + "LinkKeyPinLength", NULL); + +failed: + g_free(str); + + return info; +} + +static struct mgmt_ltk_info *get_ltk_info(GKeyFile *key_file, const char *peer, + bool master) +{ + const char *key_s, *keytype_s, *encsize_s, *ediv_s, *rand_s; + struct mgmt_ltk_info *info = NULL; + char *key; + unsigned int i; + + key_s = master ? "LongTermKey" : "SlaveLongTermKey"; + keytype_s = master ? "LongTermKeyType" : "SlaveLongTermKeyType"; + encsize_s = master ? "LongTermKeyEncSize" : "SlaveLongTermKeyEncSize"; + ediv_s = master ? "LongTermKeyEDiv" : "SlaveLongTermKeyEDiv"; + rand_s = master ? "LongTermKeyRand" : "SlaveLongTermKeyRand"; + + key = g_key_file_get_string(key_file, peer, key_s, NULL); + if (!key || strlen(key) != 32) + goto failed; + + info = g_new0(struct mgmt_ltk_info, 1); + + str2ba(peer, &info->addr.bdaddr); + + info->addr.type = g_key_file_get_integer(key_file, peer, "AddressType", + NULL); + + for (i = 0; i < sizeof(info->val); i++) + sscanf(key + (i * 2), "%02hhX", &info->val[i]); + + info->type = g_key_file_get_integer(key_file, peer, keytype_s, NULL); + + info->enc_size = g_key_file_get_integer(key_file, peer, encsize_s, + NULL); + + info->rand = g_key_file_get_uint64(key_file, peer, rand_s, NULL); + info->rand = cpu_to_le64(info->rand); + + info->ediv = g_key_file_get_integer(key_file, peer, ediv_s, NULL); + info->ediv = cpu_to_le16(info->ediv); + + info->master = master; + +failed: + g_free(key); + + return info; +} + +static struct mgmt_irk_info *get_irk_info(GKeyFile *key_file, const char *peer) +{ + struct mgmt_irk_info *info = NULL; + unsigned int i; + char *str; + + str = g_key_file_get_string(key_file, peer, "IdentityResolvingKey", + NULL); + if (!str || strlen(str) != 32) + goto failed; + + info = g_new0(struct mgmt_irk_info, 1); + + str2ba(peer, &info->addr.bdaddr); + + info->addr.type = g_key_file_get_integer(key_file, peer, "AddressType", + NULL); + + for (i = 0; i < sizeof(info->val); i++) + sscanf(str + (i * 2), "%02hhX", &info->val[i]); + +failed: + g_free(str); + + return info; +} + +static time_t device_timestamp(const struct device *dev) +{ + if (dev->bredr && dev->le) { + if (dev->le_seen > dev->bredr_seen) + return dev->le_seen; + + return dev->bredr_seen; + } + + if (dev->bredr) + return dev->bredr_seen; + + return dev->le_seen; +} + +static int device_timestamp_cmp(gconstpointer a, gconstpointer b) +{ + const struct device *deva = a; + const struct device *devb = b; + + return device_timestamp(deva) < device_timestamp(devb); +} + +static void load_devices_cache(void) +{ + GKeyFile *key_file; + gchar **devs; + gsize len = 0; + unsigned int i; + + key_file = g_key_file_new(); + + g_key_file_load_from_file(key_file, CACHE_FILE, 0, NULL); + + devs = g_key_file_get_groups(key_file, &len); + + for (i = 0; i < len; i++) { + struct device *dev; + + dev = create_device_from_info(key_file, devs[i]); + cached_devices = g_slist_prepend(cached_devices, dev); + } + + cached_devices = g_slist_sort(cached_devices, device_timestamp_cmp); + + g_strfreev(devs); + g_key_file_free(key_file); +} + +static void load_devices_info(bt_bluetooth_ready cb) +{ + GKeyFile *key_file; + gchar **devs; + gsize len = 0; + unsigned int i; + GSList *keys = NULL; + GSList *ltks = NULL; + GSList *irks = NULL; + + key_file = g_key_file_new(); + + g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL); + + devs = g_key_file_get_groups(key_file, &len); + + for (i = 0; i < len; i++) { + struct mgmt_link_key_info *key_info; + struct mgmt_ltk_info *ltk_info; + struct mgmt_irk_info *irk_info; + struct mgmt_ltk_info *slave_ltk_info; + struct device *dev; + + dev = create_device_from_info(key_file, devs[i]); + + key_info = get_key_info(key_file, devs[i]); + irk_info = get_irk_info(key_file, devs[i]); + ltk_info = get_ltk_info(key_file, devs[i], true); + slave_ltk_info = get_ltk_info(key_file, devs[i], false); + + /* + * Skip devices that have no permanent keys + * (CSRKs are loaded by create_device_from_info()) + */ + if (!dev->valid_local_csrk && !dev->valid_remote_csrk && + !key_info && !ltk_info && + !slave_ltk_info && !irk_info) { + error("Failed to load keys for %s, skipping", devs[i]); + free_device(dev); + continue; + } + + if (key_info) { + keys = g_slist_prepend(keys, key_info); + dev->bredr_paired = true; + dev->bredr_bonded = true; + } + + if (irk_info) + irks = g_slist_prepend(irks, irk_info); + + if (ltk_info) + ltks = g_slist_prepend(ltks, ltk_info); + + if (slave_ltk_info) + ltks = g_slist_prepend(ltks, slave_ltk_info); + + if (dev->valid_local_csrk || dev->valid_remote_csrk || + irk_info || ltk_info || slave_ltk_info) { + dev->le_paired = true; + dev->le_bonded = true; + } + + bonded_devices = g_slist_prepend(bonded_devices, dev); + } + + load_ltks(ltks); + g_slist_free_full(ltks, g_free); + + load_irks(irks); + g_slist_free_full(irks, g_free); + + load_link_keys(keys, cb); + g_slist_free_full(keys, g_free); + + g_strfreev(devs); + g_key_file_free(key_file); +} + +static void set_adapter_class(void) +{ + struct mgmt_cp_set_dev_class cp; + + memset(&cp, 0, sizeof(cp)); + + /* + * kernel assign the major and minor numbers straight to dev_class[0] + * and dev_class[1] without considering the proper bit shifting. + */ + cp.major = ADAPTER_MAJOR_CLASS & 0x1f; + cp.minor = ADAPTER_MINOR_CLASS << 2; + + if (mgmt_send(mgmt_if, MGMT_OP_SET_DEV_CLASS, adapter.index, sizeof(cp), + &cp, NULL, NULL, NULL) > 0) + return; + + error("Failed to set class of device"); +} + +static void enable_mps(void) +{ + uuid_t uuid, *uuid128; + + sdp_uuid16_create(&uuid, MPS_SVCLASS_ID); + uuid128 = sdp_uuid_to_uuid128(&uuid); + if (!uuid128) + return; + + register_mps(true); + adapter.uuids = g_slist_prepend(adapter.uuids, uuid128); + add_uuid(0, uuid128); +} + +static void clear_auto_connect_list_complete(uint8_t status, + uint16_t length, + const void *param, + void *user_data) +{ + if (status != MGMT_STATUS_SUCCESS) + error("Failed to clear auto connect list: %s (0x%02x)", + mgmt_errstr(status), status); +} + +static void clear_auto_connect_list(void) +{ + struct mgmt_cp_remove_device cp; + + if (!kernel_conn_control) + return; + + memset(&cp, 0, sizeof(cp)); + + if (mgmt_send(mgmt_if, MGMT_OP_REMOVE_DEVICE, adapter.index, sizeof(cp), + &cp, clear_auto_connect_list_complete, NULL, NULL) > 0) + return; + + error("Could not clear auto connect list"); +} + +static void read_info_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_info *rp = param; + bt_bluetooth_ready cb = user_data; + uint32_t missing_settings; + int err; + + DBG(""); + + if (status) { + error("Failed to read info for index %u: %s (0x%02x)", + adapter.index, mgmt_errstr(status), status); + err = -EIO; + goto failed; + } + + if (length < sizeof(*rp)) { + error("Too small read info complete response"); + err = -EIO; + goto failed; + } + + if (!bacmp(&rp->bdaddr, BDADDR_ANY)) { + error("No Bluetooth address"); + err = -ENODEV; + goto failed; + } + + load_adapter_config(); + + if (!bacmp(&adapter.bdaddr, BDADDR_ANY)) { + bacpy(&adapter.bdaddr, &rp->bdaddr); + store_adapter_config(); + } else if (bacmp(&adapter.bdaddr, &rp->bdaddr)) { + error("Bluetooth address mismatch"); + err = -ENODEV; + goto failed; + } + + if (adapter.name && g_strcmp0(adapter.name, (const char *) rp->name)) + set_adapter_name((uint8_t *)adapter.name, strlen(adapter.name)); + + set_adapter_class(); + + /* Store adapter information */ + adapter.dev_class = rp->dev_class[0] | (rp->dev_class[1] << 8) | + (rp->dev_class[2] << 16); + + adapter.supported_settings = le32_to_cpu(rp->supported_settings); + adapter.current_settings = le32_to_cpu(rp->current_settings); + + /* TODO: Register all event notification handlers */ + register_mgmt_handlers(); + + clear_uuids(); + clear_auto_connect_list(); + + set_io_capability(); + set_device_id(); + enable_mps(); + + missing_settings = adapter.current_settings ^ + adapter.supported_settings; + + if (missing_settings & MGMT_SETTING_SSP) + set_mode(MGMT_OP_SET_SSP, 0x01); + + if (missing_settings & MGMT_SETTING_BONDABLE) + set_mode(MGMT_OP_SET_BONDABLE, 0x01); + + load_devices_info(cb); + load_devices_cache(); + + return; + +failed: + cb(err, NULL); +} + +static void mgmt_index_added_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + bt_bluetooth_ready cb = user_data; + + DBG("index %u", index); + + if (adapter.index != MGMT_INDEX_NONE) { + DBG("skip event for index %u", index); + return; + } + + if (option_index != MGMT_INDEX_NONE && option_index != index) { + DBG("skip event for index %u (option %u)", index, option_index); + return; + } + + adapter.index = index; + + if (mgmt_send(mgmt_if, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, cb, NULL) == 0) { + cb(-EIO, NULL); + return; + } +} + +static void mgmt_index_removed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + DBG("index %u", index); + + if (index != adapter.index) + return; + + error("Adapter was removed. Exiting."); + raise(SIGTERM); +} + +static void read_index_list_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + bt_bluetooth_ready cb = user_data; + uint16_t num; + int i; + + DBG(""); + + if (status) { + error("%s: Failed to read index list: %s (0x%02x)", __func__, + mgmt_errstr(status), status); + goto failed; + } + + if (length < sizeof(*rp)) { + error("%s: Wrong size of read index list response", __func__); + goto failed; + } + + num = le16_to_cpu(rp->num_controllers); + + DBG("Number of controllers: %u", num); + + if (num * sizeof(uint16_t) + sizeof(*rp) != length) { + error("%s: Incorrect pkt size for index list rsp", __func__); + goto failed; + } + + if (adapter.index != MGMT_INDEX_NONE) + return; + + for (i = 0; i < num; i++) { + uint16_t index = le16_to_cpu(rp->index[i]); + + if (option_index != MGMT_INDEX_NONE && option_index != index) + continue; + + if (mgmt_send(mgmt_if, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, cb, NULL) == 0) + goto failed; + + adapter.index = index; + return; + } + + return; + +failed: + cb(-EIO, NULL); +} + +static void read_version_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_version *rp = param; + uint8_t mgmt_version, mgmt_revision; + bt_bluetooth_ready cb = user_data; + + DBG(""); + + if (status) { + error("Failed to read version information: %s (0x%02x)", + mgmt_errstr(status), status); + goto failed; + } + + if (length < sizeof(*rp)) { + error("Wrong size response"); + goto failed; + } + + mgmt_version = rp->version; + mgmt_revision = le16_to_cpu(rp->revision); + + info("Bluetooth management interface %u.%u initialized", + mgmt_version, mgmt_revision); + + if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 3)) { + error("Version 1.3 or later of management interface required"); + goto failed; + } + + /* Starting from mgmt 1.7, kernel can handle connection control */ + if (MGMT_VERSION(mgmt_version, mgmt_revision) >= MGMT_VERSION(1, 7)) { + info("Kernel connection control will be used"); + kernel_conn_control = true; + } + + mgmt_register(mgmt_if, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, + mgmt_index_added_event, cb, NULL); + mgmt_register(mgmt_if, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, + mgmt_index_removed_event, NULL, NULL); + + if (mgmt_send(mgmt_if, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, + NULL, read_index_list_complete, cb, NULL) > 0) + return; + + error("Failed to read controller index list"); + +failed: + cb(-EIO, NULL); +} + +bool bt_bluetooth_start(int index, bool mgmt_dbg, bt_bluetooth_ready cb) +{ + DBG("index %d", index); + + mgmt_if = mgmt_new_default(); + if (!mgmt_if) { + error("Failed to access management interface"); + return false; + } + + if (mgmt_dbg) + mgmt_set_debug(mgmt_if, mgmt_debug, "mgmt_if: ", NULL); + + if (mgmt_send(mgmt_if, MGMT_OP_READ_VERSION, MGMT_INDEX_NONE, 0, NULL, + read_version_complete, cb, NULL) == 0) { + error("Error sending READ_VERSION mgmt command"); + + mgmt_unref(mgmt_if); + mgmt_if = NULL; + + return false; + } + + if (index >= 0) + option_index = index; + + return true; +} + +static void shutdown_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + bt_bluetooth_stopped cb = user_data; + + if (status != MGMT_STATUS_SUCCESS) + error("Clean controller shutdown failed"); + + cb(); +} + +bool bt_bluetooth_stop(bt_bluetooth_stopped cb) +{ + struct mgmt_mode cp; + + if (adapter.index == MGMT_INDEX_NONE) + return false; + + info("Switching controller off"); + + memset(&cp, 0, sizeof(cp)); + + return mgmt_send(mgmt_if, MGMT_OP_SET_POWERED, adapter.index, + sizeof(cp), &cp, shutdown_complete, (void *)cb, + NULL) > 0; +} + +void bt_bluetooth_cleanup(void) +{ + g_free(adapter.name); + adapter.name = NULL; + + mgmt_unref(mgmt_if); + mgmt_if = NULL; +} + +static bool set_discoverable(uint8_t mode, uint16_t timeout) +{ + struct mgmt_cp_set_discoverable cp; + + memset(&cp, 0, sizeof(cp)); + cp.val = mode; + cp.timeout = cpu_to_le16(timeout); + + DBG("mode %u timeout %u", mode, timeout); + + if (mgmt_send(mgmt_if, MGMT_OP_SET_DISCOVERABLE, adapter.index, + sizeof(cp), &cp, set_mode_complete, NULL, NULL) > 0) + return true; + + error("Failed to set mode discoverable"); + + return false; +} + +static uint8_t get_adapter_address(void) +{ + uint8_t buf[6]; + + bdaddr2android(&adapter.bdaddr, buf); + + send_adapter_property(HAL_PROP_ADAPTER_ADDR, sizeof(buf), buf); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_adapter_name(void) +{ + if (!adapter.name) + return HAL_STATUS_FAILED; + + adapter_name_changed((uint8_t *) adapter.name); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_adapter_class(void) +{ + DBG(""); + + adapter_class_changed(); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t settings2type(void) +{ + bool bredr, le; + + bredr = adapter.current_settings & MGMT_SETTING_BREDR; + le = adapter.current_settings & MGMT_SETTING_LE; + + if (bredr && le) + return HAL_TYPE_DUAL; + + if (bredr && !le) + return HAL_TYPE_BREDR; + + if (!bredr && le) + return HAL_TYPE_LE; + + return 0; +} + +static uint8_t get_adapter_type(void) +{ + uint8_t type; + + DBG(""); + + type = settings2type(); + + if (!type) + return HAL_STATUS_FAILED; + + send_adapter_property(HAL_PROP_ADAPTER_TYPE, sizeof(type), &type); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_adapter_service_rec(void) +{ + DBG("Not implemented"); + + /* TODO: Add implementation */ + + return HAL_STATUS_FAILED; +} + +static uint8_t get_adapter_scan_mode(void) +{ + DBG(""); + + scan_mode_changed(); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_adapter_bonded_devices(void) +{ + uint8_t buf[sizeof(bdaddr_t) * g_slist_length(bonded_devices)]; + int i = 0; + GSList *l; + + DBG(""); + + for (l = bonded_devices; l; l = g_slist_next(l)) { + struct device *dev = l->data; + + get_device_android_addr(dev, buf + (i * sizeof(bdaddr_t))); + i++; + } + + send_adapter_property(HAL_PROP_ADAPTER_BONDED_DEVICES, + i * sizeof(bdaddr_t), buf); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_adapter_discoverable_timeout(void) +{ + send_adapter_property(HAL_PROP_ADAPTER_DISC_TIMEOUT, + sizeof(adapter.discoverable_timeout), + &adapter.discoverable_timeout); + + return HAL_STATUS_SUCCESS; +} + +static void prepare_le_features(uint8_t *le_features) +{ + le_features[0] = !!(adapter.current_settings & MGMT_SETTING_PRIVACY); + le_features[1] = adapter.max_advert_instance; + le_features[2] = adapter.rpa_offload_supported; + le_features[3] = adapter.max_irk_list_size; + le_features[4] = adapter.max_scan_filters_supported; + /* lo byte */ + le_features[5] = adapter.scan_result_storage_size; + /* hi byte */ + le_features[6] = adapter.scan_result_storage_size >> 8; + le_features[7] = adapter.activity_energy_info_supported; +} + +static uint8_t get_adapter_le_features(void) +{ + uint8_t le_features[8]; + + prepare_le_features(le_features); + + send_adapter_property(HAL_PROP_ADAPTER_LOCAL_LE_FEAT, + sizeof(le_features), le_features); + return HAL_STATUS_SUCCESS; +} + +static void handle_get_adapter_prop_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_get_adapter_prop *cmd = buf; + uint8_t status; + + switch (cmd->type) { + case HAL_PROP_ADAPTER_ADDR: + status = get_adapter_address(); + break; + case HAL_PROP_ADAPTER_NAME: + status = get_adapter_name(); + break; + case HAL_PROP_ADAPTER_UUIDS: + status = get_adapter_uuids(); + break; + case HAL_PROP_ADAPTER_CLASS: + status = get_adapter_class(); + break; + case HAL_PROP_ADAPTER_TYPE: + status = get_adapter_type(); + break; + case HAL_PROP_ADAPTER_SERVICE_REC: + status = get_adapter_service_rec(); + break; + case HAL_PROP_ADAPTER_SCAN_MODE: + status = get_adapter_scan_mode(); + break; + case HAL_PROP_ADAPTER_BONDED_DEVICES: + status = get_adapter_bonded_devices(); + break; + case HAL_PROP_ADAPTER_DISC_TIMEOUT: + status = get_adapter_discoverable_timeout(); + break; + case HAL_PROP_ADAPTER_LOCAL_LE_FEAT: + status = get_adapter_le_features(); + break; + default: + status = HAL_STATUS_FAILED; + break; + } + + if (status != HAL_STATUS_SUCCESS) + error("Failed to get adapter property (type %u status %u)", + cmd->type, status); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_GET_ADAPTER_PROP, + status); +} + +static void get_adapter_properties(void) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_adapter_props_changed *ev = (void *) buf; + uint8_t bonded[g_slist_length(bonded_devices) * sizeof(bdaddr_t)]; + uint128_t uuids[g_slist_length(adapter.uuids)]; + uint8_t android_bdaddr[6]; + uint8_t le_features[8]; + uint8_t type, mode; + size_t size, i; + GSList *l; + + size = sizeof(*ev); + + ev->status = HAL_STATUS_SUCCESS; + ev->num_props = 0; + + bdaddr2android(&adapter.bdaddr, &android_bdaddr); + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_ADDR, + sizeof(android_bdaddr), android_bdaddr); + ev->num_props++; + + if (adapter.name) { + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_NAME, + strlen(adapter.name), adapter.name); + ev->num_props++; + } + + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_CLASS, + sizeof(adapter.dev_class), &adapter.dev_class); + ev->num_props++; + + type = settings2type(); + if (type) { + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_TYPE, + sizeof(type), &type); + ev->num_props++; + } + + mode = settings2scan_mode(); + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_SCAN_MODE, + sizeof(mode), &mode); + ev->num_props++; + + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_DISC_TIMEOUT, + sizeof(adapter.discoverable_timeout), + &adapter.discoverable_timeout); + ev->num_props++; + + for (i = 0, l = bonded_devices; l; l = g_slist_next(l), i++) { + struct device *dev = l->data; + + get_device_android_addr(dev, bonded + (i * sizeof(bdaddr_t))); + } + + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_BONDED_DEVICES, + sizeof(bonded), bonded); + ev->num_props++; + + for (i = 0, l = adapter.uuids; l; l = g_slist_next(l), i++) { + uuid_t *uuid = l->data; + + memcpy(&uuids[i], &uuid->value.uuid128, sizeof(uint128_t)); + } + + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_UUIDS, sizeof(uuids), + uuids); + ev->num_props++; + + prepare_le_features(le_features); + size += fill_hal_prop(buf + size, HAL_PROP_ADAPTER_LOCAL_LE_FEAT, + sizeof(le_features), le_features); + + ev->num_props++; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_ADAPTER_PROPS_CHANGED, size, buf); +} + +static void cancel_pending_confirm_name(gpointer data, gpointer user_data) +{ + struct device *dev = data; + + mgmt_cancel(mgmt_if, dev->confirm_id); + dev->confirm_id = 0; +} + +static bool stop_discovery(uint8_t type) +{ + struct mgmt_cp_stop_discovery cp; + + cp.type = get_supported_discovery_type() & type; + + DBG("type=0x%x", cp.type); + + if (cp.type == SCAN_TYPE_NONE) + return false; + + /* Lets drop all confirm name request as we don't need it anymore */ + g_slist_foreach(cached_devices, cancel_pending_confirm_name, NULL); + + if (mgmt_send(mgmt_if, MGMT_OP_STOP_DISCOVERY, adapter.index, + sizeof(cp), &cp, NULL, NULL, NULL) > 0) + return true; + + error("Failed to stop discovery"); + return false; +} + +struct adv_user_data { + bt_le_set_advertising_done cb; + void *user_data; +}; + +static void set_advertising_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct adv_user_data *data = user_data; + + DBG(""); + + if (status) + error("Failed to set adverising %s (0x%02x))", + mgmt_errstr(status), status); + + data->cb(status, data->user_data); +} + +bool bt_le_set_advertising(bool advertising, bt_le_set_advertising_done cb, + void *user_data) +{ + struct adv_user_data *data; + uint8_t adv = advertising ? 0x01 : 0x00; + + data = new0(struct adv_user_data, 1); + data->cb = cb; + data->user_data = user_data; + + if (mgmt_send(mgmt_if, MGMT_OP_SET_ADVERTISING, adapter.index, + sizeof(adv), &adv, set_advertising_cb, data, free) > 0) + return true; + + error("Failed to set advertising"); + free(data); + return false; +} + +bool bt_le_register(bt_le_device_found cb) +{ + if (gatt_device_found_cb) + return false; + + gatt_device_found_cb = cb; + + return true; +} + +void bt_le_unregister(void) +{ + gatt_device_found_cb = NULL; +} + +bool bt_le_discovery_stop(bt_le_discovery_stopped cb) +{ + if (!(adapter.current_settings & MGMT_SETTING_POWERED)) + return false; + + adapter.le_scanning = false; + + if (adapter.cur_discovery_type != SCAN_TYPE_LE) { + if (cb) + cb(); + + return true; + } + + if (!stop_discovery(SCAN_TYPE_LE)) + return false; + + gatt_discovery_stopped_cb = cb; + adapter.exp_discovery_type = SCAN_TYPE_NONE; + + return true; +} + +bool bt_le_discovery_start(void) +{ + if (!(adapter.current_settings & MGMT_SETTING_POWERED)) + return false; + + adapter.le_scanning = true; + + /* + * If core is discovering - just set expected next scan type. + * It will be triggered in case current scan session is almost done + * i.e. we missed LE phase in interleaved scan, or we're trying to + * connect to device that was already discovered. + */ + if (adapter.cur_discovery_type != SCAN_TYPE_NONE) { + adapter.exp_discovery_type = SCAN_TYPE_LE; + return true; + } + + if (start_discovery(SCAN_TYPE_LE)) + return true; + + return false; +} + +struct read_rssi_user_data { + bt_read_device_rssi_done cb; + void *user_data; +}; + +static void read_device_rssi_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_get_conn_info *rp = param; + struct read_rssi_user_data *data = user_data; + + DBG(""); + + if (status) + error("Failed to get conn info: %s (0x%02x))", + mgmt_errstr(status), status); + + if (length < sizeof(*rp)) { + error("Wrong size of get conn info response"); + return; + } + + data->cb(status, &rp->addr.bdaddr, rp->rssi, data->user_data); +} + +bool bt_read_device_rssi(const bdaddr_t *addr, bt_read_device_rssi_done cb, + void *user_data) +{ + struct device *dev; + struct read_rssi_user_data *data; + struct mgmt_cp_get_conn_info cp; + + dev = find_device(addr); + if (!dev) + return false; + + memcpy(&cp.addr.bdaddr, addr, sizeof(cp.addr.bdaddr)); + cp.addr.type = dev->bredr ? BDADDR_BREDR : dev->bdaddr_type; + + data = new0(struct read_rssi_user_data, 1); + data->cb = cb; + data->user_data = user_data; + + if (!mgmt_send(mgmt_if, MGMT_OP_GET_CONN_INFO, adapter.index, + sizeof(cp), &cp, read_device_rssi_cb, data, free)) { + free(data); + error("Failed to get conn info"); + return false; + } + + return true; +} + +bool bt_get_csrk(const bdaddr_t *addr, bool local, uint8_t key[16], + uint32_t *sign_cnt, bool *authenticated) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return false; + + if (local && dev->valid_local_csrk) { + if (key) + memcpy(key, dev->local_csrk, 16); + + if (sign_cnt) + *sign_cnt = dev->local_sign_cnt; + + if (authenticated) + *authenticated = dev->local_csrk_auth; + } else if (!local && dev->valid_remote_csrk) { + if (key) + memcpy(key, dev->remote_csrk, 16); + + if (sign_cnt) + *sign_cnt = dev->remote_sign_cnt; + + if (authenticated) + *authenticated = dev->remote_csrk_auth; + } else { + return false; + } + + return true; +} + +static void store_sign_counter(struct device *dev, bool local) +{ + const char *sign_cnt_s; + uint32_t sign_cnt; + GKeyFile *key_file; + + gsize length = 0; + char addr[18]; + char *data; + + key_file = g_key_file_new(); + if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) { + g_key_file_free(key_file); + return; + } + + ba2str(&dev->bdaddr, addr); + + sign_cnt_s = local ? "LocalCSRKSignCounter" : "RemoteCSRKSignCounter"; + sign_cnt = local ? dev->local_sign_cnt : dev->remote_sign_cnt; + + g_key_file_set_integer(key_file, addr, sign_cnt_s, sign_cnt); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(DEVICES_FILE, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +void bt_update_sign_counter(const bdaddr_t *addr, bool local, uint32_t val) +{ + struct device *dev; + + dev = find_device(addr); + if (!dev) + return; + + if (local) + dev->local_sign_cnt = val; + else + dev->remote_sign_cnt = val; + + store_sign_counter(dev, local); +} + +static uint8_t set_adapter_scan_mode(const void *buf, uint16_t len) +{ + const uint8_t *mode = buf; + bool conn, disc, cur_conn, cur_disc; + + if (len != sizeof(*mode)) { + error("Invalid set scan mode size (%u bytes), terminating", + len); + raise(SIGTERM); + return HAL_STATUS_FAILED; + } + + cur_conn = adapter.current_settings & MGMT_SETTING_CONNECTABLE; + cur_disc = adapter.current_settings & MGMT_SETTING_DISCOVERABLE; + + DBG("connectable %u discoverable %d mode %u", cur_conn, cur_disc, + *mode); + + switch (*mode) { + case HAL_ADAPTER_SCAN_MODE_NONE: + if (!cur_conn && !cur_disc) + goto done; + + conn = false; + disc = false; + break; + case HAL_ADAPTER_SCAN_MODE_CONN: + if (cur_conn && !cur_disc) + goto done; + + conn = true; + disc = false; + break; + case HAL_ADAPTER_SCAN_MODE_CONN_DISC: + if (cur_conn && cur_disc) + goto done; + + conn = true; + disc = true; + break; + default: + return HAL_STATUS_FAILED; + } + + if (cur_conn != conn) { + if (!set_mode(MGMT_OP_SET_CONNECTABLE, conn ? 0x01 : 0x00)) + return HAL_STATUS_FAILED; + } + + if (cur_disc != disc && conn) { + if (!set_discoverable(disc ? 0x01 : 0x00, 0)) + return HAL_STATUS_FAILED; + } + + return HAL_STATUS_SUCCESS; + +done: + /* Android expects property changed callback */ + scan_mode_changed(); + + return HAL_STATUS_SUCCESS; +} + +static void handle_set_adapter_prop_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_set_adapter_prop *cmd = buf; + uint8_t status; + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid set adapter prop cmd (0x%x), terminating", + cmd->type); + raise(SIGTERM); + return; + } + + switch (cmd->type) { + case HAL_PROP_ADAPTER_SCAN_MODE: + status = set_adapter_scan_mode(cmd->val, cmd->len); + break; + case HAL_PROP_ADAPTER_NAME: + status = set_adapter_name(cmd->val, cmd->len); + break; + case HAL_PROP_ADAPTER_DISC_TIMEOUT: + status = set_adapter_discoverable_timeout(cmd->val, cmd->len); + break; + default: + DBG("Unhandled property type 0x%x", cmd->type); + status = HAL_STATUS_FAILED; + break; + } + + if (status != HAL_STATUS_SUCCESS) + error("Failed to set adapter property (type %u status %u)", + cmd->type, status); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_SET_ADAPTER_PROP, + status); +} + +static void pair_device_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_pair_device *rp = param; + struct device *dev; + + DBG("status %u", status); + + dev = find_device(&rp->addr.bdaddr); + if (!dev) + return; + + /* + * Update pairing and paired status. Bonded status will be updated once + * any link key come + */ + update_device_state(dev, rp->addr.type, status_mgmt2hal(status), false, + !status, false); + + if (status == MGMT_STATUS_SUCCESS) + queue_foreach(paired_cb_list, send_paired_notification, dev); +} + +static uint8_t select_device_bearer(struct device *dev) +{ + uint8_t res; + + if (dev->bredr && dev->le) { + if (dev->le_seen > dev->bredr_seen) + res = dev->bdaddr_type; + else + res = BDADDR_BREDR; + } else { + res = dev->bredr ? BDADDR_BREDR : dev->bdaddr_type; + } + + DBG("Selected bearer %d", res); + + return res; +} + +uint8_t bt_device_last_seen_bearer(const bdaddr_t *bdaddr) +{ + struct device *dev; + + dev = find_device(bdaddr); + if (!dev) + return BDADDR_BREDR; + + return select_device_bearer(dev); +} + +static void handle_create_bond_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_create_bond *cmd = buf; + struct device *dev; + uint8_t status; + struct mgmt_cp_pair_device cp; + + dev = get_device_android(cmd->bdaddr); + + cp.io_cap = DEFAULT_IO_CAPABILITY; + cp.addr.type = select_device_bearer(dev); + bacpy(&cp.addr.bdaddr, &dev->bdaddr); + + /* TODO: Handle transport parameter */ + if (cmd->transport > BT_TRANSPORT_LE) { + status = HAL_STATUS_INVALID; + goto fail; + } + + if (device_is_paired(dev, cp.addr.type)) { + status = HAL_STATUS_FAILED; + goto fail; + } + + if (mgmt_send(mgmt_if, MGMT_OP_PAIR_DEVICE, adapter.index, sizeof(cp), + &cp, pair_device_complete, NULL, NULL) == 0) { + status = HAL_STATUS_FAILED; + goto fail; + } + + status = HAL_STATUS_SUCCESS; + + update_device_state(dev, cp.addr.type, HAL_STATUS_SUCCESS, true, false, + false); + +fail: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CREATE_BOND, + status); +} + +static void handle_cancel_bond_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_cancel_bond *cmd = buf; + struct mgmt_addr_info cp; + struct device *dev; + uint8_t status; + + dev = find_device_android(cmd->bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto failed; + } + + cp.type = select_device_bearer(dev); + bacpy(&cp.bdaddr, &dev->bdaddr); + + if (mgmt_reply(mgmt_if, MGMT_OP_CANCEL_PAIR_DEVICE, adapter.index, + sizeof(cp), &cp, NULL, NULL, NULL) == 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CANCEL_BOND, + status); +} + +static void send_unpaired_notification(void *data, void *user_data) +{ + bt_unpaired_device_cb cb = data; + struct mgmt_addr_info *addr = user_data; + + cb(&addr->bdaddr); +} + +static void unpair_device_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_unpair_device *rp = param; + struct device *dev; + + DBG("status %u", status); + + if (status != MGMT_STATUS_SUCCESS && status != MGMT_STATUS_NOT_PAIRED) + return; + + dev = find_device(&rp->addr.bdaddr); + if (!dev) + return; + + update_device_state(dev, rp->addr.type, HAL_STATUS_SUCCESS, false, + false, false); + + /* Cast rp->addr to (void *) since queue_foreach don't take const */ + + if (!dev->le_paired && !dev->bredr_paired) + queue_foreach(unpaired_cb_list, send_unpaired_notification, + (void *)&rp->addr); +} + +static void handle_remove_bond_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_remove_bond *cmd = buf; + struct mgmt_cp_unpair_device cp; + struct device *dev; + uint8_t status; + + dev = find_device_android(cmd->bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto failed; + } + + cp.disconnect = 1; + bacpy(&cp.addr.bdaddr, &dev->bdaddr); + + if (dev->le_paired) { + cp.addr.type = dev->bdaddr_type; + + if (mgmt_send(mgmt_if, MGMT_OP_UNPAIR_DEVICE, adapter.index, + sizeof(cp), &cp, unpair_device_complete, + NULL, NULL) == 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + } + + if (dev->bredr_paired) { + cp.addr.type = BDADDR_BREDR; + + if (mgmt_send(mgmt_if, MGMT_OP_UNPAIR_DEVICE, adapter.index, + sizeof(cp), &cp, unpair_device_complete, + NULL, NULL) == 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_REMOVE_BOND, + status); +} + +static void handle_pin_reply_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_pin_reply *cmd = buf; + uint8_t status; + bdaddr_t bdaddr; + char addr[18]; + + android2bdaddr(cmd->bdaddr, &bdaddr); + ba2str(&bdaddr, addr); + + DBG("%s accept %u pin_len %u", addr, cmd->accept, cmd->pin_len); + + if (!cmd->accept && cmd->pin_len) { + status = HAL_STATUS_INVALID; + goto failed; + } + + if (cmd->accept) { + struct mgmt_cp_pin_code_reply rp; + + memset(&rp, 0, sizeof(rp)); + + bacpy(&rp.addr.bdaddr, &bdaddr); + rp.addr.type = BDADDR_BREDR; + rp.pin_len = cmd->pin_len; + memcpy(rp.pin_code, cmd->pin_code, rp.pin_len); + + if (mgmt_reply(mgmt_if, MGMT_OP_PIN_CODE_REPLY, adapter.index, + sizeof(rp), &rp, NULL, NULL, NULL) == 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + } else { + struct mgmt_cp_pin_code_neg_reply rp; + + bacpy(&rp.addr.bdaddr, &bdaddr); + rp.addr.type = BDADDR_BREDR; + + if (mgmt_reply(mgmt_if, MGMT_OP_PIN_CODE_NEG_REPLY, + adapter.index, sizeof(rp), &rp, + NULL, NULL, NULL) == 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + } + + status = HAL_STATUS_SUCCESS; +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_PIN_REPLY, + status); +} + +static uint8_t user_confirm_reply(const bdaddr_t *bdaddr, uint8_t type, + bool accept) +{ + struct mgmt_addr_info cp; + uint16_t opcode; + + if (accept) + opcode = MGMT_OP_USER_CONFIRM_REPLY; + else + opcode = MGMT_OP_USER_CONFIRM_NEG_REPLY; + + bacpy(&cp.bdaddr, bdaddr); + cp.type = type; + + if (mgmt_reply(mgmt_if, opcode, adapter.index, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return HAL_STATUS_SUCCESS; + + return HAL_STATUS_FAILED; +} + +static uint8_t user_passkey_reply(const bdaddr_t *bdaddr, uint8_t type, + bool accept, uint32_t passkey) +{ + unsigned int id; + + if (accept) { + struct mgmt_cp_user_passkey_reply cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = type; + cp.passkey = cpu_to_le32(passkey); + + id = mgmt_reply(mgmt_if, MGMT_OP_USER_PASSKEY_REPLY, + adapter.index, sizeof(cp), &cp, + NULL, NULL, NULL); + } else { + struct mgmt_cp_user_passkey_neg_reply cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = type; + + id = mgmt_reply(mgmt_if, MGMT_OP_USER_PASSKEY_NEG_REPLY, + adapter.index, sizeof(cp), &cp, + NULL, NULL, NULL); + } + + if (id == 0) + return HAL_STATUS_FAILED; + + return HAL_STATUS_SUCCESS; +} + +static void handle_ssp_reply_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_ssp_reply *cmd = buf; + struct device *dev; + uint8_t status; + char addr[18]; + + /* TODO should parameters sanity be verified here? */ + + dev = find_device_android(cmd->bdaddr); + if (!dev) + return; + + ba2str(&dev->bdaddr, addr); + + DBG("%s variant %u accept %u", addr, cmd->ssp_variant, cmd->accept); + + switch (cmd->ssp_variant) { + case HAL_SSP_VARIANT_CONFIRM: + case HAL_SSP_VARIANT_CONSENT: + status = user_confirm_reply(&dev->bdaddr, + select_device_bearer(dev), + cmd->accept); + break; + case HAL_SSP_VARIANT_ENTRY: + status = user_passkey_reply(&dev->bdaddr, + select_device_bearer(dev), + cmd->accept, cmd->passkey); + break; + case HAL_SSP_VARIANT_NOTIF: + status = HAL_STATUS_SUCCESS; + break; + default: + status = HAL_STATUS_INVALID; + break; + } + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_SSP_REPLY, + status); +} + +static void handle_get_remote_services_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_get_remote_services *cmd = buf; + uint8_t status; + bdaddr_t addr; + + android2bdaddr(&cmd->bdaddr, &addr); + + status = browse_remote_sdp(&addr); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICES, status); +} + +static uint8_t get_device_uuids(struct device *dev) +{ + send_device_uuids_notif(dev); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_device_class(struct device *dev) +{ + send_device_property(dev, HAL_PROP_DEVICE_CLASS, sizeof(dev->class), + &dev->class); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_device_type(struct device *dev) +{ + uint8_t type = get_device_android_type(dev); + + send_device_property(dev, HAL_PROP_DEVICE_TYPE, sizeof(type), &type); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_device_service_rec(struct device *dev) +{ + DBG("Not implemented"); + + /* TODO */ + + return HAL_STATUS_FAILED; +} + +static uint8_t get_device_friendly_name(struct device *dev) +{ + if (!dev->friendly_name) + return HAL_STATUS_FAILED; + + send_device_property(dev, HAL_PROP_DEVICE_FRIENDLY_NAME, + strlen(dev->friendly_name), dev->friendly_name); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_device_rssi(struct device *dev) +{ + if (!dev->rssi) + return HAL_STATUS_FAILED; + + send_device_property(dev, HAL_PROP_DEVICE_RSSI, sizeof(dev->rssi), + &dev->rssi); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t get_device_version_info(struct device *dev) +{ + DBG("Not implemented"); + + /* TODO */ + + return HAL_STATUS_FAILED; +} + +static uint8_t get_device_timestamp(struct device *dev) +{ + uint32_t timestamp; + + timestamp = device_timestamp(dev); + + send_device_property(dev, HAL_PROP_DEVICE_TIMESTAMP, sizeof(timestamp), + ×tamp); + + return HAL_STATUS_SUCCESS; +} + +static void get_remote_device_props(struct device *dev) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_remote_device_props *ev = (void *) buf; + uint128_t uuids[g_slist_length(dev->uuids)]; + uint8_t android_type; + uint32_t timestamp; + size_t size, i; + GSList *l; + + memset(buf, 0, sizeof(buf)); + + size = sizeof(*ev); + + ev->status = HAL_STATUS_SUCCESS; + get_device_android_addr(dev, ev->bdaddr); + + android_type = get_device_android_type(dev); + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_TYPE, + sizeof(android_type), &android_type); + ev->num_props++; + + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_CLASS, + sizeof(dev->class), &dev->class); + ev->num_props++; + + if (dev->rssi) { + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_RSSI, + sizeof(dev->rssi), &dev->rssi); + ev->num_props++; + } + + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_NAME, + strlen(dev->name), dev->name); + ev->num_props++; + + if (dev->friendly_name) { + size += fill_hal_prop(buf + size, + HAL_PROP_DEVICE_FRIENDLY_NAME, + strlen(dev->friendly_name), + dev->friendly_name); + ev->num_props++; + } + + for (i = 0, l = dev->uuids; l; l = g_slist_next(l), i++) + memcpy(&uuids[i], l->data, sizeof(uint128_t)); + + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_UUIDS, sizeof(uuids), + uuids); + ev->num_props++; + + timestamp = get_device_timestamp(dev); + + size += fill_hal_prop(buf + size, HAL_PROP_DEVICE_TIMESTAMP, + sizeof(timestamp), ×tamp); + ev->num_props++; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_EV_REMOTE_DEVICE_PROPS, size, buf); +} + +static void send_bonded_devices_props(void) +{ + GSList *l; + + for (l = bonded_devices; l; l = g_slist_next(l)) { + struct device *dev = l->data; + + get_remote_device_props(dev); + } +} + +static void handle_enable_cmd(const void *buf, uint16_t len) +{ + uint8_t status; + + /* + * Framework expects all properties to be emitted while enabling + * adapter + */ + get_adapter_properties(); + + /* Sent also properties of bonded devices */ + send_bonded_devices_props(); + + if (adapter.current_settings & MGMT_SETTING_POWERED) { + status = HAL_STATUS_SUCCESS; + goto reply; + } + + if (!set_mode(MGMT_OP_SET_POWERED, 0x01)) { + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_ENABLE, status); +} + +static void handle_disable_cmd(const void *buf, uint16_t len) +{ + uint8_t status; + + if (!(adapter.current_settings & MGMT_SETTING_POWERED)) { + status = HAL_STATUS_SUCCESS; + goto reply; + } + + /* Cancel all pending requests. Need it in case of ongoing paring */ + mgmt_cancel_index(mgmt_if, adapter.index); + + if (!set_mode(MGMT_OP_SET_POWERED, 0x00)) { + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DISABLE, status); +} + +static void handle_get_adapter_props_cmd(const void *buf, uint16_t len) +{ + get_adapter_properties(); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_ADAPTER_PROPS, HAL_STATUS_SUCCESS); +} + +static void handle_get_remote_device_props_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_get_remote_device_props *cmd = buf; + struct device *dev; + uint8_t status; + + dev = find_device_android(cmd->bdaddr); + if (!dev) { + status = HAL_STATUS_INVALID; + goto failed; + } + + get_remote_device_props(dev); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROPS, status); +} + +static void handle_get_remote_device_prop_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_get_remote_device_prop *cmd = buf; + struct device *dev; + uint8_t status; + + dev = find_device_android(cmd->bdaddr); + if (!dev) { + status = HAL_STATUS_INVALID; + goto failed; + } + + switch (cmd->type) { + case HAL_PROP_DEVICE_NAME: + status = get_device_name(dev); + break; + case HAL_PROP_DEVICE_UUIDS: + status = get_device_uuids(dev); + break; + case HAL_PROP_DEVICE_CLASS: + status = get_device_class(dev); + break; + case HAL_PROP_DEVICE_TYPE: + status = get_device_type(dev); + break; + case HAL_PROP_DEVICE_SERVICE_REC: + status = get_device_service_rec(dev); + break; + case HAL_PROP_DEVICE_FRIENDLY_NAME: + status = get_device_friendly_name(dev); + break; + case HAL_PROP_DEVICE_RSSI: + status = get_device_rssi(dev); + break; + case HAL_PROP_DEVICE_VERSION_INFO: + status = get_device_version_info(dev); + break; + case HAL_PROP_DEVICE_TIMESTAMP: + status = get_device_timestamp(dev); + break; + default: + status = HAL_STATUS_FAILED; + break; + } + + if (status != HAL_STATUS_SUCCESS) + error("Failed to get device property (type %u status %u)", + cmd->type, status); + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROP, status); +} + +static uint8_t set_device_friendly_name(struct device *dev, const uint8_t *val, + uint16_t len) +{ + DBG(""); + + g_free(dev->friendly_name); + dev->friendly_name = g_strndup((const char *) val, len); + + if (dev->bredr_paired || dev->le_paired) + store_device_info(dev, DEVICES_FILE); + else + store_device_info(dev, CACHE_FILE); + + return HAL_STATUS_SUCCESS; +} + +static uint8_t set_device_version_info(struct device *dev) +{ + DBG("Not implemented"); + + /* TODO */ + + return HAL_STATUS_FAILED; +} + +static void handle_set_remote_device_prop_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_set_remote_device_prop *cmd = buf; + struct device *dev; + uint8_t status; + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid set remote device prop cmd (0x%x), terminating", + cmd->type); + raise(SIGTERM); + return; + } + + dev = find_device_android(cmd->bdaddr); + if (!dev) { + status = HAL_STATUS_INVALID; + goto failed; + } + + switch (cmd->type) { + case HAL_PROP_DEVICE_FRIENDLY_NAME: + status = set_device_friendly_name(dev, cmd->val, cmd->len); + break; + case HAL_PROP_DEVICE_VERSION_INFO: + status = set_device_version_info(dev); + break; + default: + status = HAL_STATUS_FAILED; + break; + } + + if (status != HAL_STATUS_SUCCESS) + error("Failed to set device property (type %u status %u)", + cmd->type, status); + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SET_REMOTE_DEVICE_PROP, status); +} + +static void handle_get_remote_service_rec_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_get_remote_service_rec *cmd = buf; + uint8_t status; + bdaddr_t addr; + + android2bdaddr(&cmd->bdaddr, &addr); + + status = find_remote_sdp_rec(&addr, cmd->uuid); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICE_REC, status); +} + +static void handle_start_discovery_cmd(const void *buf, uint16_t len) +{ + uint8_t status; + + if (!(adapter.current_settings & MGMT_SETTING_POWERED)) { + status = HAL_STATUS_NOT_READY; + goto failed; + } + + switch (adapter.cur_discovery_type) { + case SCAN_TYPE_DUAL: + case SCAN_TYPE_BREDR: + break; + case SCAN_TYPE_NONE: + if (!start_discovery(SCAN_TYPE_DUAL)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case SCAN_TYPE_LE: + if (get_supported_discovery_type() == SCAN_TYPE_LE) + break; + + if (!stop_discovery(SCAN_TYPE_LE)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + adapter.exp_discovery_type = SCAN_TYPE_DUAL; + break; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_START_DISCOVERY, + status); +} + +static void handle_cancel_discovery_cmd(const void *buf, uint16_t len) +{ + uint8_t status; + + if (!(adapter.current_settings & MGMT_SETTING_POWERED)) { + status = HAL_STATUS_NOT_READY; + goto failed; + } + + switch (adapter.cur_discovery_type) { + case SCAN_TYPE_NONE: + break; + case SCAN_TYPE_LE: + if (get_supported_discovery_type() != SCAN_TYPE_LE) + break; + + if (adapter.exp_discovery_type == SCAN_TYPE_LE) { + status = HAL_STATUS_BUSY; + goto failed; + } + + if (!stop_discovery(SCAN_TYPE_LE)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case SCAN_TYPE_DUAL: + case SCAN_TYPE_BREDR: + if (!stop_discovery(SCAN_TYPE_DUAL)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (adapter.exp_discovery_type != SCAN_TYPE_LE) + adapter.exp_discovery_type = SCAN_TYPE_NONE; + + break; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CANCEL_DISCOVERY, + status); +} + +static void handle_dut_mode_conf_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_dut_mode_conf *cmd = buf; + char path[FILENAME_MAX]; + uint8_t status; + int fd, ret; + + DBG("enable %u", cmd->enable); + + snprintf(path, sizeof(path), DUT_MODE_FILE, adapter.index); + + fd = open(path, O_WRONLY); + if (fd < 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (cmd->enable) + ret = write(fd, "1", sizeof("1")); + else + ret = write(fd, "0", sizeof("0")); + + if (ret < 0) + status = HAL_STATUS_FAILED; + else + status = HAL_STATUS_SUCCESS; + + close(fd); + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DUT_MODE_CONF, + status); +} + +static void handle_dut_mode_send_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_dut_mode_send *cmd = buf; + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid dut mode send cmd, terminating"); + raise(SIGTERM); + return; + } + + error("dut_mode_send not supported (cmd opcode %u)", cmd->opcode); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DUT_MODE_SEND, + HAL_STATUS_FAILED); +} + +static void handle_le_test_mode_cmd(const void *buf, uint16_t len) +{ + const struct hal_cmd_le_test_mode *cmd = buf; + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid le test mode cmd, terminating"); + raise(SIGTERM); + return; + } + + error("le_test_mode not supported (cmd opcode %u)", cmd->opcode); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_LE_TEST_MODE, + HAL_STATUS_FAILED); +} + +static void handle_get_connection_state(const void *buf, uint16_t len) +{ + const struct hal_cmd_get_connection_state *cmd = buf; + struct hal_rsp_get_connection_state rsp; + struct device *dev; + char address[18]; + bdaddr_t bdaddr; + + android2bdaddr(cmd->bdaddr, &bdaddr); + ba2str(&bdaddr, address); + + dev = find_device_android(cmd->bdaddr); + if (dev && dev->connected) + rsp.connection_state = 1; + else + rsp.connection_state = 0; + + DBG("%s %u", address, rsp.connection_state); + + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_CONNECTION_STATE, sizeof(rsp), &rsp, + -1); +} + +static void handle_read_energy_info(const void *buf, uint16_t len) +{ + DBG(""); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, HAL_OP_READ_ENERGY_INFO, + HAL_STATUS_UNSUPPORTED); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_ENABLE */ + { handle_enable_cmd, false, 0 }, + /* HAL_OP_DISABLE */ + { handle_disable_cmd, false, 0 }, + /* HAL_OP_GET_ADAPTER_PROPS */ + { handle_get_adapter_props_cmd, false, 0 }, + /* HAL_OP_GET_ADAPTER_PROP */ + { handle_get_adapter_prop_cmd, false, + sizeof(struct hal_cmd_get_adapter_prop) }, + /* HAL_OP_SET_ADAPTER_PROP */ + { handle_set_adapter_prop_cmd, true, + sizeof(struct hal_cmd_set_adapter_prop) }, + /* HAL_OP_GET_REMOTE_DEVICE_PROPS */ + { handle_get_remote_device_props_cmd, false, + sizeof(struct hal_cmd_get_remote_device_props) }, + /* HAL_OP_GET_REMOTE_DEVICE_PROP */ + { handle_get_remote_device_prop_cmd, false, + sizeof(struct hal_cmd_get_remote_device_prop) }, + /* HAL_OP_SET_REMOTE_DEVICE_PROP */ + { handle_set_remote_device_prop_cmd, true, + sizeof(struct hal_cmd_set_remote_device_prop) }, + /* HAL_OP_GET_REMOTE_SERVICE_REC */ + { handle_get_remote_service_rec_cmd, false, + sizeof(struct hal_cmd_get_remote_service_rec) }, + /* HAL_OP_GET_REMOTE_SERVICES */ + { handle_get_remote_services_cmd, false, + sizeof(struct hal_cmd_get_remote_services) }, + /* HAL_OP_START_DISCOVERY */ + { handle_start_discovery_cmd, false, 0 }, + /* HAL_OP_CANCEL_DISCOVERY */ + { handle_cancel_discovery_cmd, false, 0 }, + /* HAL_OP_CREATE_BOND */ + { handle_create_bond_cmd, false, sizeof(struct hal_cmd_create_bond) }, + /* HAL_OP_REMOVE_BOND */ + { handle_remove_bond_cmd, false, sizeof(struct hal_cmd_remove_bond) }, + /* HAL_OP_CANCEL_BOND */ + {handle_cancel_bond_cmd, false, sizeof(struct hal_cmd_cancel_bond) }, + /* HAL_OP_PIN_REPLY */ + { handle_pin_reply_cmd, false, sizeof(struct hal_cmd_pin_reply) }, + /* HAL_OP_SSP_REPLY */ + { handle_ssp_reply_cmd, false, sizeof(struct hal_cmd_ssp_reply) }, + /* HAL_OP_DUT_MODE_CONF */ + { handle_dut_mode_conf_cmd, false, + sizeof(struct hal_cmd_dut_mode_conf) }, + /* HAL_OP_DUT_MODE_SEND */ + { handle_dut_mode_send_cmd, true, + sizeof(struct hal_cmd_dut_mode_send) }, + /* HAL_OP_LE_TEST_MODE */ + { handle_le_test_mode_cmd, true, sizeof(struct hal_cmd_le_test_mode) }, + /* HAL_OP_GET_CONNECTION_STATE */ + { handle_get_connection_state, false, + sizeof(struct hal_cmd_get_connection_state) }, + /* HAL_OP_READ_ENERGY_INFO */ + { handle_read_energy_info, false, 0 }, +}; + +bool bt_bluetooth_register(struct ipc *ipc, uint8_t mode) +{ + uint32_t missing_settings; + + DBG("mode 0x%x", mode); + + unpaired_cb_list = queue_new(); + paired_cb_list = queue_new(); + + missing_settings = adapter.current_settings ^ + adapter.supported_settings; + + switch (mode) { + case HAL_MODE_DEFAULT: + if (missing_settings & MGMT_SETTING_BREDR) + set_mode(MGMT_OP_SET_BREDR, 0x01); + + if (missing_settings & MGMT_SETTING_LE) + set_mode(MGMT_OP_SET_LE, 0x01); + break; + case HAL_MODE_LE: + /* Fail if controller does not support LE */ + if (!(adapter.supported_settings & MGMT_SETTING_LE)) { + error("LE Mode not supported by controller"); + goto failed; + } + + /* If LE it is not yet enabled then enable it */ + if (!(adapter.current_settings & MGMT_SETTING_LE)) + set_mode(MGMT_OP_SET_LE, 0x01); + + /* Disable BR/EDR if it is enabled */ + if (adapter.current_settings & MGMT_SETTING_BREDR) + set_mode(MGMT_OP_SET_BREDR, 0x00); + break; + case HAL_MODE_BREDR: + /* Fail if controller does not support BR/EDR */ + if (!(adapter.supported_settings & MGMT_SETTING_BREDR)) { + error("BR/EDR Mode not supported"); + goto failed; + } + + /* Enable BR/EDR if it is not enabled */ + if (missing_settings & MGMT_SETTING_BREDR) + set_mode(MGMT_OP_SET_BREDR, 0x01); + + /* + * According to Core Spec 4.0 host should not disable LE in + * controller if it was enabled (Vol 2. Part E. 7.3.79). + * Core Spec 4.1 removed this limitation and chips seem to be + * handling this just fine anyway. + */ + if (adapter.current_settings & MGMT_SETTING_LE) + set_mode(MGMT_OP_SET_LE, 0x00); + break; + default: + error("Unknown mode 0x%x", mode); + goto failed; + } + + /* Requested mode is set now, let's enable secure connection */ + if (missing_settings & MGMT_SETTING_SECURE_CONN) + set_mode(MGMT_OP_SET_SECURE_CONN, 0x01); + + /* Set initial default name */ + if (!adapter.name) { + adapter.name = g_strdup(bt_config_get_model()); + set_adapter_name((uint8_t *)adapter.name, strlen(adapter.name)); + } + + hal_ipc = ipc; + + ipc_register(hal_ipc, HAL_SERVICE_ID_BLUETOOTH, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; + +failed: + queue_destroy(unpaired_cb_list, NULL); + unpaired_cb_list = NULL; + queue_destroy(paired_cb_list, NULL); + paired_cb_list = NULL; + + return false; +} + +void bt_bluetooth_unregister(void) +{ + DBG(""); + + g_slist_free_full(bonded_devices, (GDestroyNotify) free_device); + bonded_devices = NULL; + + g_slist_free_full(cached_devices, (GDestroyNotify) free_device); + cached_devices = NULL; + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_CORE); + hal_ipc = NULL; + + queue_destroy(unpaired_cb_list, NULL); + unpaired_cb_list = NULL; + + queue_destroy(paired_cb_list, NULL); + paired_cb_list = NULL; +}
diff --git a/android/bluetooth.h b/android/bluetooth.h new file mode 100644 index 0000000..4b17209 --- /dev/null +++ b/android/bluetooth.h
@@ -0,0 +1,90 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +typedef void (*bt_bluetooth_ready)(int err, const bdaddr_t *addr); +bool bt_bluetooth_start(int index, bool mgmt_dbg, bt_bluetooth_ready cb); + +typedef void (*bt_bluetooth_stopped)(void); +bool bt_bluetooth_stop(bt_bluetooth_stopped cb); + +void bt_bluetooth_cleanup(void); + +bool bt_bluetooth_register(struct ipc *ipc, uint8_t mode); +void bt_bluetooth_unregister(void); + +int bt_adapter_add_record(sdp_record_t *rec, uint8_t svc_hint); +void bt_adapter_remove_record(uint32_t handle); + +typedef void (*bt_le_device_found)(const bdaddr_t *addr, int rssi, + uint16_t eir_len, const void *eir, + bool connectable, bool bonded); +bool bt_le_register(bt_le_device_found cb); +void bt_le_unregister(void); + +bool bt_le_discovery_start(void); + +typedef void (*bt_le_discovery_stopped)(void); +bool bt_le_discovery_stop(bt_le_discovery_stopped cb); + +typedef void (*bt_le_set_advertising_done)(uint8_t status, void *user_data); +bool bt_le_set_advertising(bool advertising, bt_le_set_advertising_done cb, + void *user_data); + +uint8_t bt_get_device_android_type(const bdaddr_t *addr); +bool bt_is_device_le(const bdaddr_t *addr); +uint8_t bt_device_last_seen_bearer(const bdaddr_t *bdaddr); + +const char *bt_get_adapter_name(void); +bool bt_device_is_bonded(const bdaddr_t *bdaddr); +bool bt_device_set_uuids(const bdaddr_t *bdaddr, GSList *uuids); + +typedef void (*bt_read_device_rssi_done)(uint8_t status, const bdaddr_t *addr, + int8_t rssi, void *user_data); +bool bt_read_device_rssi(const bdaddr_t *addr, bt_read_device_rssi_done cb, + void *user_data); + +bool bt_get_csrk(const bdaddr_t *addr, bool local, uint8_t key[16], + uint32_t *sign_cnt, bool *authenticated); + +void bt_update_sign_counter(const bdaddr_t *addr, bool local, uint32_t val); + +void bt_store_gatt_ccc(const bdaddr_t *addr, uint16_t value); + +uint16_t bt_get_gatt_ccc(const bdaddr_t *addr); + +const bdaddr_t *bt_get_id_addr(const bdaddr_t *addr, uint8_t *type); + +bool bt_kernel_conn_control(void); + +bool bt_auto_connect_add(const bdaddr_t *addr); + +void bt_auto_connect_remove(const bdaddr_t *addr); + +typedef void (*bt_unpaired_device_cb)(const bdaddr_t *addr); +bool bt_unpaired_register(bt_unpaired_device_cb cb); +void bt_unpaired_unregister(bt_unpaired_device_cb cb); + +typedef void (*bt_paired_device_cb)(const bdaddr_t *addr); +bool bt_paired_register(bt_paired_device_cb cb); +void bt_paired_unregister(bt_paired_device_cb cb); +bool bt_is_pairing(const bdaddr_t *addr);
diff --git a/android/bluetoothd-snoop.c b/android/bluetoothd-snoop.c new file mode 100644 index 0000000..4b09663 --- /dev/null +++ b/android/bluetoothd-snoop.c
@@ -0,0 +1,262 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#if defined(ANDROID) +#include <sys/capability.h> +#endif + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/mgmt.h" + +#include "src/shared/mainloop.h" +#include "src/shared/btsnoop.h" +#include "src/log.h" + +#define DEFAULT_SNOOP_FILE "/sdcard/btsnoop_hci.log" + +static struct btsnoop *snoop = NULL; +static uint8_t monitor_buf[BTSNOOP_MAX_PACKET_SIZE]; +static int monitor_fd = -1; + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static uint32_t get_flags_from_opcode(uint16_t opcode) +{ + switch (opcode) { + case BTSNOOP_OPCODE_NEW_INDEX: + case BTSNOOP_OPCODE_DEL_INDEX: + break; + case BTSNOOP_OPCODE_COMMAND_PKT: + return 0x02; + case BTSNOOP_OPCODE_EVENT_PKT: + return 0x03; + case BTSNOOP_OPCODE_ACL_TX_PKT: + return 0x00; + case BTSNOOP_OPCODE_ACL_RX_PKT: + return 0x01; + case BTSNOOP_OPCODE_SCO_TX_PKT: + case BTSNOOP_OPCODE_SCO_RX_PKT: + break; + case BTSNOOP_OPCODE_OPEN_INDEX: + case BTSNOOP_OPCODE_CLOSE_INDEX: + break; + } + + return 0xff; +} + +static void data_callback(int fd, uint32_t events, void *user_data) +{ + unsigned char control[32]; + struct mgmt_hdr hdr; + struct msghdr msg; + struct iovec iov[2]; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(monitor_fd); + return; + } + + iov[0].iov_base = &hdr; + iov[0].iov_len = MGMT_HDR_SIZE; + iov[1].iov_base = monitor_buf; + iov[1].iov_len = sizeof(monitor_buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + while (true) { + struct cmsghdr *cmsg; + struct timeval *tv = NULL; + struct timeval ctv; + uint16_t opcode, index, pktlen; + uint32_t flags; + ssize_t len; + + len = recvmsg(monitor_fd, &msg, MSG_DONTWAIT); + if (len < 0) + break; + + if (len < MGMT_HDR_SIZE) + break; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + + if (cmsg->cmsg_type == SCM_TIMESTAMP) { + memcpy(&ctv, CMSG_DATA(cmsg), sizeof(ctv)); + tv = &ctv; + } + } + + opcode = btohs(hdr.opcode); + index = btohs(hdr.index); + pktlen = btohs(hdr.len); + + if (index) + continue; + + flags = get_flags_from_opcode(opcode); + if (flags != 0xff) + btsnoop_write(snoop, tv, flags, 0, monitor_buf, pktlen); + } +} + +static int open_monitor(const char *path) +{ + struct sockaddr_hci addr; + int opt = 1; + + snoop = btsnoop_create(path, BTSNOOP_FORMAT_HCI); + if (!snoop) + return -1; + + monitor_fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (monitor_fd < 0) + goto failed; + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = HCI_CHANNEL_MONITOR; + + if (bind(monitor_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) + goto failed_close; + + if (setsockopt(monitor_fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) + < 0) + goto failed_close; + + mainloop_add_fd(monitor_fd, EPOLLIN, data_callback, NULL, NULL); + + return 0; + +failed_close: + close(monitor_fd); + monitor_fd = -1; + +failed: + btsnoop_unref(snoop); + snoop = NULL; + + return -1; +} + +static void close_monitor(void) +{ + btsnoop_unref(snoop); + snoop = NULL; + + close(monitor_fd); + monitor_fd = -1; +} + +static void set_capabilities(void) +{ +#if defined(ANDROID) + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; + + /* + * CAP_NET_RAW: for snooping + * CAP_DAC_READ_SEARCH: override path search permissions + */ + cap.effective = cap.permitted = + CAP_TO_MASK(CAP_NET_RAW) | + CAP_TO_MASK(CAP_DAC_READ_SEARCH); + cap.inheritable = 0; + + /* TODO: Move to cap_set_proc once bionic support it */ + if (capset(&header, &cap) < 0) + exit(EXIT_FAILURE); +#endif +} + +int main(int argc, char *argv[]) +{ + const char *path; + sigset_t mask; + + __btd_log_init(NULL, 0); + + DBG(""); + + set_capabilities(); + + if (argc > 1) + path = argv[1]; + else + path = DEFAULT_SNOOP_FILE; + + mainloop_init(); + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + mainloop_set_signal(&mask, signal_callback, NULL, NULL); + + if (!strcmp(DEFAULT_SNOOP_FILE, path)) + rename(DEFAULT_SNOOP_FILE, DEFAULT_SNOOP_FILE ".old"); + + if (open_monitor(path) < 0) { + error("bluetoothd_snoop: start failed"); + return EXIT_FAILURE; + } + + info("bluetoothd_snoop: started"); + + mainloop_run(); + + close_monitor(); + + info("bluetoothd_snoop: stopped"); + + __btd_log_cleanup(); + + return EXIT_SUCCESS; +}
diff --git a/android/bluetoothd-wrapper.c b/android/bluetoothd-wrapper.c new file mode 100644 index 0000000..7f668da --- /dev/null +++ b/android/bluetoothd-wrapper.c
@@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> + +#include <cutils/properties.h> + +#include "hal-utils.h" + +#define VALGRIND_BIN "/system/bin/valgrind" + +#define BLUETOOTHD_BIN "/system/bin/bluetoothd-main" + +static void run_valgrind(int debug, int mgmt_dbg) +{ + char *prg_argv[7]; + char *prg_envp[3]; + + prg_argv[0] = VALGRIND_BIN; + prg_argv[1] = "--leak-check=full"; + prg_argv[2] = "--track-origins=yes"; + prg_argv[3] = BLUETOOTHD_BIN; + prg_argv[4] = debug ? "-d" : NULL; + prg_argv[5] = mgmt_dbg ? "--mgmt-debug" : NULL; + prg_argv[6] = NULL; + + prg_envp[0] = "G_SLICE=always-malloc"; + prg_envp[1] = "G_DEBUG=gc-friendly"; + prg_envp[2] = NULL; + + execve(prg_argv[0], prg_argv, prg_envp); +} + +static void run_bluetoothd(int debug, int mgmt_dbg) +{ + char *prg_argv[4]; + char *prg_envp[1]; + + prg_argv[0] = BLUETOOTHD_BIN; + prg_argv[1] = debug ? "-d" : NULL; + prg_argv[2] = mgmt_dbg ? "--mgmt-debug" : NULL; + prg_argv[3] = NULL; + + prg_envp[0] = NULL; + + execve(prg_argv[0], prg_argv, prg_envp); +} + +int main(int argc, char *argv[]) +{ + char value[PROPERTY_VALUE_MAX]; + int debug = 0; + int mgmt_dbg = 0; + + if (get_config("debug", value, NULL) > 0 && + (!strcasecmp(value, "true") || atoi(value) > 0)) + debug = 1; + + if (get_config("mgmtdbg", value, NULL) > 0 && + (!strcasecmp(value, "true") || atoi(value) > 0)) { + debug = 1; + mgmt_dbg = 1; + } + + if (get_config("valgrind", value, NULL) > 0 && + (!strcasecmp(value, "true") || atoi(value) > 0)) + run_valgrind(debug, mgmt_dbg); + + /* + * In case we failed to execute Valgrind, try to run bluetoothd + * without it + */ + run_bluetoothd(debug, mgmt_dbg); + + return 0; +}
diff --git a/android/bluetoothd.te b/android/bluetoothd.te new file mode 100644 index 0000000..532bfbb --- /dev/null +++ b/android/bluetoothd.te
@@ -0,0 +1,47 @@ +type bluetoothd, domain; +type bluetoothd_exec, exec_type, file_type; +type bluetoothd_main_exec, exec_type, file_type; + +# Start bluetoothd from init +init_daemon_domain(bluetoothd) + +# Data file accesses +allow bluetoothd bluetooth_data_file:dir w_dir_perms; +allow bluetoothd bluetooth_data_file:notdevfile_class_set create_file_perms; + +allow bluetoothd self:capability { setuid net_admin net_bind_service net_raw }; +allow bluetoothd kernel:system module_request; + +# TODO: this may be romoved for userbuild where we don't use bluetoothd_wrapper +allow bluetoothd bluetoothd_main_exec:file { execute execute_no_trans read open }; + +# IPC socket communication +allow bluetoothd self:socket { create_socket_perms accept listen setopt getopt }; + +# Allow clients to use a socket provided by the bluetooth app. +allow bluetoothd { bluetooth mediaserver }:unix_stream_socket connectto; + +# Allow system app to use sockets and fds +allow bluetooth bluetoothd:fd use; +allow bluetooth bluetoothd:unix_stream_socket rw_socket_perms; + +# Allow user bluetooth apps to use sockets and fds +allow bluetoothdomain bluetoothd:fd use; +allow bluetoothdomain bluetoothd:unix_stream_socket { getopt setopt getattr read write ioctl shutdown }; + +# Other domains that can create and use bluetooth sockets. +allow bluetoothdomain self:socket create_socket_perms; + +#This we might should put to mediaserver.te ? +allow mediaserver bluetoothd:fd use; +allow mediaserver bluetoothd:socket rw_socket_perms; + +# needs /system/bin/log access +allow bluetoothd devpts:chr_file rw_file_perms; + +# access to uhid device +allow bluetoothd uhid_device:chr_file rw_file_perms; + +# tethering +allow bluetoothd self:udp_socket create_socket_perms; +allow bluetoothd self:tcp_socket { create ioctl };
diff --git a/android/bluetoothd_snoop.te b/android/bluetoothd_snoop.te new file mode 100644 index 0000000..ef817b5 --- /dev/null +++ b/android/bluetoothd_snoop.te
@@ -0,0 +1,17 @@ +type bluetoothd_snoop, domain; +type bluetoothd_snoop_exec, exec_type, file_type; + +# Start bluetoothd_snoop from init +init_daemon_domain(bluetoothd_snoop) + +# directory search and read caps +allow bluetoothd_snoop self:capability dac_read_search; +# use raw and packet sockets caps +allow bluetoothd_snoop self:capability net_raw; + +# monitor socket access +allow bluetoothd_snoop self:socket { create bind setopt read }; + +# sdcard access +allow bluetoothd_snoop fuse:dir w_dir_perms; +allow bluetoothd_snoop fuse:file create_file_perms;
diff --git a/android/client/haltest.c b/android/client/haltest.c new file mode 100644 index 0000000..92cd811 --- /dev/null +++ b/android/client/haltest.c
@@ -0,0 +1,477 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <poll.h> +#include <unistd.h> +#include <getopt.h> + +#include "if-main.h" +#include "terminal.h" +#include "pollhandler.h" +#include "history.h" + +static void process_line(char *line_buffer); + +const struct interface *interfaces[] = { + &audio_if, + &sco_if, + &bluetooth_if, + &av_if, + &rc_if, + &gatt_if, + &gatt_client_if, + &gatt_server_if, + &hf_if, + &hh_if, + &pan_if, + &hl_if, + &sock_if, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + &hf_client_if, + &mce_if, + &ctrl_rc_if, + &av_sink_if, +#endif + NULL +}; + +static struct method commands[]; + +struct method *get_method(struct method *methods, const char *name) +{ + while (strcmp(methods->name, "") != 0) { + if (strcmp(methods->name, name) == 0) + return methods; + methods++; + } + + return NULL; +} + +/* function returns interface of given name or NULL if not found */ +const struct interface *get_interface(const char *name) +{ + int i; + + for (i = 0; interfaces[i] != NULL; ++i) { + if (strcmp(interfaces[i]->name, name) == 0) + break; + } + + return interfaces[i]; +} + +int haltest_error(const char *format, ...) +{ + va_list args; + int ret; + va_start(args, format); + ret = terminal_vprint(format, args); + va_end(args); + return ret; +} + +int haltest_info(const char *format, ...) +{ + va_list args; + int ret; + va_start(args, format); + ret = terminal_vprint(format, args); + va_end(args); + return ret; +} + +int haltest_warn(const char *format, ...) +{ + va_list args; + int ret; + va_start(args, format); + ret = terminal_vprint(format, args); + va_end(args); + return ret; +} + +static void help_print_interface(const struct interface *i) +{ + struct method *m; + + for (m = i->methods; strcmp(m->name, "") != 0; m++) + haltest_info("%s %s %s\n", i->name, m->name, + (m->help ? m->help : "")); +} + +/* Help completion */ +static void help_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 2) + *enum_func = interface_name; +} + +/* Help execution */ +static void help_p(int argc, const char **argv) +{ + const struct method *m = commands; + const struct interface **ip = interfaces; + const struct interface *i; + + if (argc == 1) { + terminal_print("haltest allows to call Android HAL methods.\n"); + terminal_print("\nAvailable commands:\n"); + while (0 != strcmp(m->name, "")) { + terminal_print("\t%s %s\n", m->name, + (m->help ? m->help : "")); + m++; + } + + terminal_print("\nAvailable interfaces to use:\n"); + while (NULL != *ip) { + terminal_print("\t%s\n", (*ip)->name); + ip++; + } + + terminal_print("\nTo get help on methods for each interface type:\n"); + terminal_print("\n\thelp <inerface>\n"); + terminal_print("\nBasic scenario:\n\tbluetooth init\n"); + terminal_print("\tbluetooth enable\n\tbluetooth start_discovery\n"); + terminal_print("\tbluetooth get_profile_interface handsfree\n"); + terminal_print("\thandsfree init\n\n"); + return; + } + + i = get_interface(argv[1]); + if (i == NULL) { + haltest_error("No such interface\n"); + return; + } + + help_print_interface(i); +} + +/* quit/exit execution */ +static void quit_p(int argc, const char **argv) +{ + char cleanup_audio[] = "audio cleanup"; + + close_hw_bt_dev(); + process_line(cleanup_audio); + + exit(0); +} + +static int fd_stack[10]; +static int fd_stack_pointer = 0; + +static void stdin_handler(struct pollfd *pollfd); + +static void process_file(const char *name) +{ + int fd = open(name, O_RDONLY); + + if (fd < 0) { + haltest_error("Can't open file: %s for reading\n", name); + return; + } + + if (fd_stack_pointer >= 10) { + haltest_error("To many open files\n"); + close(fd); + return; + } + + fd_stack[fd_stack_pointer++] = fd; + poll_unregister_fd(fd_stack[fd_stack_pointer - 2], stdin_handler); + poll_register_fd(fd_stack[fd_stack_pointer - 1], POLLIN, stdin_handler); +} + +static void source_p(int argc, const char **argv) +{ + if (argc < 2) { + haltest_error("No file specified"); + return; + } + + process_file(argv[1]); +} + +/* Commands available without interface */ +static struct method commands[] = { + STD_METHODCH(help, "[<interface>]"), + STD_METHOD(quit), + METHOD("exit", quit_p, NULL, NULL), + STD_METHODH(source, "<file>"), + END_METHOD +}; + +/* Gets comman by name */ +struct method *get_command(const char *name) +{ + return get_method(commands, name); +} + +/* Function to enumerate interface names */ +const char *interface_name(void *v, int i) +{ + return interfaces[i] ? interfaces[i]->name : NULL; +} + +/* Function to enumerate command and interface names */ +const char *command_name(void *v, int i) +{ + int cmd_cnt = NELEM(commands); + + if (i >= cmd_cnt) + return interface_name(v, i - cmd_cnt); + else + return commands[i].name; +} + +/* + * This function changes input parameter line_buffer so it has + * null termination after each token (due to strtok) + * Output argv is filled with pointers to arguments + * returns number of tokens parsed - argc + */ +static int command_line_to_argv(char *line_buffer, char *argv[], int argv_size) +{ + static const char *token_breaks = "\r\n\t "; + char *token; + int argc = 0; + + token = strtok(line_buffer, token_breaks); + while (token != NULL && argc < (int) argv_size) { + argv[argc++] = token; + token = strtok(NULL, token_breaks); + } + + return argc; +} + +static void process_line(char *line_buffer) +{ + char *argv[50]; + int argc; + int i = 0; + struct method *m; + + argc = command_line_to_argv(line_buffer, argv, 50); + if (argc < 1) + return; + + while (interfaces[i] != NULL) { + if (strcmp(interfaces[i]->name, argv[0])) { + i++; + continue; + } + + if (argc < 2 || strcmp(argv[1], "?") == 0) { + help_print_interface(interfaces[i]); + return; + } + + m = get_method(interfaces[i]->methods, argv[1]); + if (m != NULL) { + m->func(argc, (const char **) argv); + return; + } + + haltest_error("No function %s found\n", argv[1]); + return; + } + /* No interface, try commands */ + m = get_command(argv[0]); + if (m == NULL) + haltest_error("No such command %s\n", argv[0]); + else + m->func(argc, (const char **) argv); +} + +/* called when there is something on stdin */ +static void stdin_handler(struct pollfd *pollfd) +{ + char buf[10]; + + if (pollfd->revents & POLLIN) { + int count = read(fd_stack[fd_stack_pointer - 1], buf, 10); + + if (count > 0) { + int i; + + for (i = 0; i < count; ++i) + terminal_process_char(buf[i], process_line); + return; + } + } + + if (fd_stack_pointer > 1) + poll_register_fd(fd_stack[fd_stack_pointer - 2], POLLIN, + stdin_handler); + if (fd_stack_pointer > 0) { + poll_unregister_fd(fd_stack[--fd_stack_pointer], stdin_handler); + + if (fd_stack[fd_stack_pointer]) + close(fd_stack[fd_stack_pointer]); + } +} + +static void usage(void) +{ + printf("haltest Android Bluetooth HAL testing tool\n" + "Usage:\n"); + printf("\thaltest [options]\n"); + printf("options:\n" + "\t-i --ivi Initialize only IVI interfaces\n" + "\t-n, --no-init Don't initialize any interfaces\n" + "\t-v --version Print version\n" + "\t-h, --help Show help options\n"); +} + +static void print_version(void) +{ + printf("haltest version %s\n", VERSION); +} + +static const struct option main_options[] = { + { "no-init", no_argument, NULL, 'n' }, + { "ivi", no_argument, NULL, 'i' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL } +}; + +static bool no_init = false; +static bool ivi_only = false; + +static void parse_command_line(int argc, char *argv[]) +{ + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "inhv", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'n': + no_init = true; + break; + case 'i': + ivi_only = true; + break; + case 'h': + usage(); + exit(0); + case 'v': + print_version(); + exit(0); + default: + putchar('\n'); + exit(-1); + break; + } + } +} + +static const char * const interface_names[] = { + BT_PROFILE_HANDSFREE_ID, + BT_PROFILE_ADVANCED_AUDIO_ID, + BT_PROFILE_AV_RC_ID, + BT_PROFILE_HEALTH_ID, + BT_PROFILE_HIDHOST_ID, + BT_PROFILE_PAN_ID, + BT_PROFILE_GATT_ID, + BT_PROFILE_SOCKETS_ID, + NULL +}; + +static const char * const ivi_interface_inames[] = { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + BT_PROFILE_HANDSFREE_CLIENT_ID, + BT_PROFILE_MAP_CLIENT_ID, + BT_PROFILE_AV_RC_CTRL_ID, + BT_PROFILE_ADVANCED_AUDIO_SINK_ID, +#endif + NULL +}; + +static void init(const char * const *inames) +{ + const struct method *m; + const char *argv[4]; + char init_audio[] = "audio init"; + char init_sco[] = "sco init"; + char init_bt[] = "bluetooth init"; + uint32_t i; + + process_line(init_audio); + process_line(init_sco); + process_line(init_bt); + + m = get_interface_method("bluetooth", "get_profile_interface"); + + while (*inames) { + argv[2] = *inames; + m->func(3, argv); + inames++; + } + + /* Init what is available to init */ + for (i = 3; i < NELEM(interfaces) - 1; ++i) { + m = get_interface_method(interfaces[i]->name, "init"); + if (m != NULL) + m->func(2, argv); + } +} + +int main(int argc, char **argv) +{ + struct stat rcstat; + + parse_command_line(argc, argv); + + terminal_setup(); + + if (!no_init) { + if (ivi_only) + init(ivi_interface_inames); + else + init(interface_names); + } + + history_restore(".haltest_history"); + + fd_stack[fd_stack_pointer++] = 0; + /* Register command line handler */ + poll_register_fd(0, POLLIN, stdin_handler); + + if (stat(".haltestrc", &rcstat) == 0 && (rcstat.st_mode & S_IFREG) != 0) + process_file(".haltestrc"); + + poll_dispatch_loop(); + + return 0; +}
diff --git a/android/client/history.c b/android/client/history.c new file mode 100644 index 0000000..ca4664c --- /dev/null +++ b/android/client/history.c
@@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include "history.h" + +/* Very simple history storage for easy usage of tool */ + +#define HISTORY_DEPTH 40 +#define LINE_SIZE 200 +static char lines[HISTORY_DEPTH][LINE_SIZE]; +static int last_line = 0; +static int history_size = 0; + +/* TODO: Storing history not implemented yet */ +void history_store(const char *filename) +{ +} + +/* Restoring history from file */ +void history_restore(const char *filename) +{ + char line[1000]; + FILE *f = fopen(filename, "rt"); + + if (f == NULL) + return; + + for (;;) { + if (fgets(line, 1000, f) != NULL) { + int l = strlen(line); + + while (l > 0 && isspace(line[--l])) + line[l] = 0; + + if (l > 0) + history_add_line(line); + } else + break; + } + + fclose(f); +} + +/* Add new line to history buffer */ +void history_add_line(const char *line) +{ + if (line == NULL || strlen(line) == 0) + return; + + if (strcmp(line, lines[last_line]) == 0) + return; + + last_line = (last_line + 1) % HISTORY_DEPTH; + strncpy(&lines[last_line][0], line, LINE_SIZE - 1); + if (history_size < HISTORY_DEPTH) + history_size++; +} + +/* + * Get n-th line from history + * 0 - means latest + * -1 - means oldest + * return -1 if there is no such line + */ +int history_get_line(int n, char *buf, int buf_size) +{ + if (n == -1) + n = history_size - 1; + + if (n >= history_size || buf_size == 0 || n < 0) + return -1; + + strncpy(buf, + &lines[(HISTORY_DEPTH + last_line - n) % HISTORY_DEPTH][0], + buf_size - 1); + buf[buf_size - 1] = 0; + + return n; +}
diff --git a/android/client/history.h b/android/client/history.h new file mode 100644 index 0000000..26085b5 --- /dev/null +++ b/android/client/history.h
@@ -0,0 +1,21 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +void history_store(const char *filename); +void history_restore(const char *filename); +void history_add_line(const char *line); +int history_get_line(int n, char *buf, int buf_size);
diff --git a/android/client/if-audio.c b/android/client/if-audio.c new file mode 100644 index 0000000..65e2f2f --- /dev/null +++ b/android/client/if-audio.c
@@ -0,0 +1,535 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <pthread.h> +#include <unistd.h> +#include <math.h> + +#include "if-main.h" +#include "../hal-utils.h" + +audio_hw_device_t *if_audio = NULL; +static struct audio_stream_out *stream_out = NULL; + +static size_t buffer_size = 0; +static pthread_t play_thread = 0; +static pthread_mutex_t outstream_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +enum state { + STATE_STOPPED, + STATE_STOPPING, + STATE_PLAYING, + STATE_SUSPENDED, + STATE_MAX +}; + +SINTMAP(audio_channel_mask_t, -1, "(AUDIO_CHANNEL_INVALID)") + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_LOW_FREQUENCY), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_SIDE_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_SIDE_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_MONO), + DELEMENT(AUDIO_CHANNEL_OUT_STEREO), + DELEMENT(AUDIO_CHANNEL_OUT_QUAD), +#if ANDROID_VERSION < PLATFORM_VER(5, 0, 0) + DELEMENT(AUDIO_CHANNEL_OUT_SURROUND), +#else + DELEMENT(AUDIO_CHANNEL_OUT_QUAD_BACK), + DELEMENT(AUDIO_CHANNEL_OUT_QUAD_SIDE), + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1_BACK), + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1_SIDE), +#endif + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1), + DELEMENT(AUDIO_CHANNEL_OUT_7POINT1), + DELEMENT(AUDIO_CHANNEL_OUT_ALL), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), +ENDMAP + +SINTMAP(audio_format_t, -1, "(AUDIO_FORMAT_INVALID)") + DELEMENT(AUDIO_FORMAT_DEFAULT), + DELEMENT(AUDIO_FORMAT_PCM), + DELEMENT(AUDIO_FORMAT_MP3), + DELEMENT(AUDIO_FORMAT_AMR_NB), + DELEMENT(AUDIO_FORMAT_AMR_WB), + DELEMENT(AUDIO_FORMAT_AAC), + DELEMENT(AUDIO_FORMAT_HE_AAC_V1), + DELEMENT(AUDIO_FORMAT_HE_AAC_V2), + DELEMENT(AUDIO_FORMAT_VORBIS), + DELEMENT(AUDIO_FORMAT_MAIN_MASK), + DELEMENT(AUDIO_FORMAT_SUB_MASK), + DELEMENT(AUDIO_FORMAT_PCM_16_BIT), + DELEMENT(AUDIO_FORMAT_PCM_8_BIT), + DELEMENT(AUDIO_FORMAT_PCM_32_BIT), + DELEMENT(AUDIO_FORMAT_PCM_8_24_BIT), +ENDMAP + +static int current_state = STATE_STOPPED; + +#define SAMPLERATE 44100 +static short sample[SAMPLERATE]; +static uint16_t sample_pos; + +static void init_p(int argc, const char **argv) +{ + int err; + const hw_module_t *module; + audio_hw_device_t *device; + + err = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, + AUDIO_HARDWARE_MODULE_ID_A2DP, &module); + if (err) { + haltest_error("hw_get_module_by_class returned %d\n", err); + return; + } + + err = audio_hw_device_open(module, &device); + if (err) { + haltest_error("audio_hw_device_open returned %d\n", err); + return; + } + + if_audio = device; +} + +static int feed_from_file(short *buffer, void *data) +{ + FILE *in = data; + return fread(buffer, buffer_size, 1, in); +} + +static int feed_from_generator(short *buffer, void *data) +{ + size_t i = 0; + float volume = 0.5; + float *freq = data; + float f = 1; + + if (freq) + f = *freq; + + /* buffer_size is in bytes but we are using buffer of shorts (2 bytes)*/ + for (i = 0; i < buffer_size / sizeof(*buffer) - 1;) { + if (sample_pos >= SAMPLERATE) + sample_pos = sample_pos % SAMPLERATE; + + /* Use the same sample for both channels */ + buffer[i++] = sample[sample_pos] * volume; + buffer[i++] = sample[sample_pos] * volume; + + sample_pos += f; + } + + return buffer_size; +} + +static void prepare_sample(void) +{ + int x; + double s; + + haltest_info("Preparing audio sample...\n"); + + for (x = 0; x < SAMPLERATE; x++) { + /* prepare sinusoidal 1Hz sample */ + s = (2.0 * 3.14159) * ((double)x / SAMPLERATE); + s = sin(s); + + /* remap <-1, 1> to signed 16bit PCM range */ + sample[x] = s * 32767; + } + + sample_pos = 0; +} + +static void *playback_thread(void *data) +{ + int (*filbuff_cb) (short*, void*); + short buffer[buffer_size / sizeof(short)]; + size_t len = 0; + ssize_t w_len = 0; + FILE *in = data; + void *cb_data = NULL; + float freq = 440.0; + + /* Use file or fall back to generator */ + if (in) { + filbuff_cb = feed_from_file; + cb_data = in; + } else { + prepare_sample(); + filbuff_cb = feed_from_generator; + cb_data = &freq; + } + + pthread_mutex_lock(&state_mutex); + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); + + do { + pthread_mutex_lock(&state_mutex); + + if (current_state == STATE_STOPPING) { + pthread_mutex_unlock(&state_mutex); + break; + } else if (current_state == STATE_SUSPENDED) { + pthread_mutex_unlock(&state_mutex); + usleep(500); + continue; + } + + pthread_mutex_unlock(&state_mutex); + + len = filbuff_cb(buffer, cb_data); + + pthread_mutex_lock(&outstream_mutex); + if (!stream_out) { + pthread_mutex_unlock(&outstream_mutex); + break; + } + + w_len = stream_out->write(stream_out, buffer, buffer_size); + pthread_mutex_unlock(&outstream_mutex); + } while (len && w_len > 0); + + if (in) + fclose(in); + + pthread_mutex_lock(&state_mutex); + current_state = STATE_STOPPED; + pthread_mutex_unlock(&state_mutex); + + haltest_info("Done playing.\n"); + + return NULL; +} + +static void play_p(int argc, const char **argv) +{ + const char *fname = NULL; + FILE *in = NULL; + + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_error("Invalid audio file path.\n"); + haltest_info("Using sound generator.\n"); + } else { + fname = argv[2]; + in = fopen(fname, "r"); + + if (in == NULL) { + haltest_error("Cannot open file: %s\n", fname); + return; + } + haltest_info("Playing file: %s\n", fname); + } + + if (buffer_size == 0) { + haltest_error("Invalid buffer size. Was stream_out opened?\n"); + goto fail; + } + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + haltest_error("Already playing or stream suspended!\n"); + pthread_mutex_unlock(&state_mutex); + goto fail; + } + pthread_mutex_unlock(&state_mutex); + + if (pthread_create(&play_thread, NULL, playback_thread, in) != 0) { + haltest_error("Cannot create playback thread!\n"); + goto fail; + } + + return; +fail: + if (in) + fclose(in); +} + +static void stop_p(int argc, const char **argv) +{ + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_STOPPED || current_state == STATE_STOPPING) { + pthread_mutex_unlock(&state_mutex); + return; + } + + current_state = STATE_STOPPING; + pthread_mutex_unlock(&state_mutex); + + pthread_mutex_lock(&outstream_mutex); + stream_out->common.standby(&stream_out->common); + pthread_mutex_unlock(&outstream_mutex); +} + +static void open_output_stream_p(int argc, const char **argv) +{ + int err; + + RETURN_IF_NULL(if_audio); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_PLAYING) { + haltest_error("Already playing!\n"); + pthread_mutex_unlock(&state_mutex); + return; + } + pthread_mutex_unlock(&state_mutex); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + err = if_audio->open_output_stream(if_audio, + 0, + AUDIO_DEVICE_OUT_ALL_A2DP, + AUDIO_OUTPUT_FLAG_NONE, + NULL, + &stream_out, NULL); +#else + err = if_audio->open_output_stream(if_audio, + 0, + AUDIO_DEVICE_OUT_ALL_A2DP, + AUDIO_OUTPUT_FLAG_NONE, + NULL, + &stream_out); +#endif + if (err < 0) { + haltest_error("open output stream returned %d\n", err); + return; + } + + buffer_size = stream_out->common.get_buffer_size(&stream_out->common); + if (buffer_size == 0) + haltest_error("Invalid buffer size received!\n"); + else + haltest_info("Using buffer size: %zu\n", buffer_size); +} + +static void close_output_stream_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + stop_p(argc, argv); + + haltest_info("Waiting for playback thread...\n"); + pthread_join(play_thread, NULL); + + if_audio->close_output_stream(if_audio, stream_out); + + stream_out = NULL; + buffer_size = 0; +} + +static void cleanup_p(int argc, const char **argv) +{ + int err; + + RETURN_IF_NULL(if_audio); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + pthread_mutex_unlock(&state_mutex); + close_output_stream_p(0, NULL); + } else { + pthread_mutex_unlock(&state_mutex); + } + + err = audio_hw_device_close(if_audio); + if (err < 0) { + haltest_error("audio_hw_device_close returned %d\n", err); + return; + } + + if_audio = NULL; +} + +static void suspend_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_PLAYING) { + pthread_mutex_unlock(&state_mutex); + return; + } + current_state = STATE_SUSPENDED; + pthread_mutex_unlock(&state_mutex); + + pthread_mutex_lock(&outstream_mutex); + stream_out->common.standby(&stream_out->common); + pthread_mutex_unlock(&outstream_mutex); +} + +static void resume_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_SUSPENDED) + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); +} + +static void get_latency_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + haltest_info("Output audio stream latency: %d\n", + stream_out->get_latency(stream_out)); +} + +static void get_buffer_size_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + haltest_info("Current output buffer size: %zu\n", + stream_out->common.get_buffer_size(&stream_out->common)); +} + +static void get_channels_p(int argc, const char **argv) +{ + audio_channel_mask_t channels; + + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + channels = stream_out->common.get_channels(&stream_out->common); + + haltest_info("Channels: %s\n", audio_channel_mask_t2str(channels)); +} + +static void get_format_p(int argc, const char **argv) +{ + audio_format_t format; + + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + format = stream_out->common.get_format(&stream_out->common); + + haltest_info("Format: %s\n", audio_format_t2str(format)); +} + +static void get_sample_rate_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + haltest_info("Current sample rate: %d\n", + stream_out->common.get_sample_rate(&stream_out->common)); +} + +static void get_parameters_p(int argc, const char **argv) +{ + const char *keystr; + + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_info("No keys given.\n"); + keystr = ""; + } else { + keystr = argv[2]; + } + + haltest_info("Current parameters: %s\n", + stream_out->common.get_parameters(&stream_out->common, + keystr)); +} + +static void set_parameters_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_error("No key=value; pairs given.\n"); + return; + } + + stream_out->common.set_parameters(&stream_out->common, argv[2]); +} + +static void set_sample_rate_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + RETURN_IF_NULL(stream_out); + + if (argc < 3) + return; + + stream_out->common.set_sample_rate(&stream_out->common, atoi(argv[2])); +} + +static void init_check_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio); + + haltest_info("Init check result: %d\n", if_audio->init_check(if_audio)); +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHOD(cleanup), + STD_METHOD(open_output_stream), + STD_METHOD(close_output_stream), + STD_METHODH(play, "<path to pcm file>"), + STD_METHOD(stop), + STD_METHOD(suspend), + STD_METHOD(resume), + STD_METHOD(get_latency), + STD_METHOD(get_buffer_size), + STD_METHOD(get_channels), + STD_METHOD(get_format), + STD_METHOD(get_sample_rate), + STD_METHODH(get_parameters, "<A2dpSuspended;closing>"), + STD_METHODH(set_parameters, "<A2dpSuspended=value;closing=value>"), + STD_METHODH(set_sample_rate, "<sample rate>"), + STD_METHOD(init_check), + END_METHOD +}; + +const struct interface audio_if = { + .name = "audio", + .methods = methods +};
diff --git a/android/client/if-av-sink.c b/android/client/if-av-sink.c new file mode 100644 index 0000000..3087dcf --- /dev/null +++ b/android/client/if-av-sink.c
@@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include "if-main.h" +#include "../hal-utils.h" + +const btav_interface_t *if_av_sink = NULL; + +SINTMAP(btav_connection_state_t, -1, "(unknown)") + DELEMENT(BTAV_CONNECTION_STATE_DISCONNECTED), + DELEMENT(BTAV_CONNECTION_STATE_CONNECTING), + DELEMENT(BTAV_CONNECTION_STATE_CONNECTED), + DELEMENT(BTAV_CONNECTION_STATE_DISCONNECTING), +ENDMAP + +SINTMAP(btav_audio_state_t, -1, "(unknown)") + DELEMENT(BTAV_AUDIO_STATE_REMOTE_SUSPEND), + DELEMENT(BTAV_AUDIO_STATE_STOPPED), + DELEMENT(BTAV_AUDIO_STATE_STARTED), +ENDMAP + +static char last_addr[MAX_ADDR_STR_LEN]; + +static void connection_state(btav_connection_state_t state, + bt_bdaddr_t *bd_addr) +{ + haltest_info("(sink) %s: connection_state=%s remote_bd_addr=%s\n", + __func__, btav_connection_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +static void audio_state(btav_audio_state_t state, bt_bdaddr_t *bd_addr) +{ + haltest_info("(sink) %s: audio_state=%s remote_bd_addr=%s\n", __func__, + btav_audio_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +static void audio_config(bt_bdaddr_t *bd_addr, uint32_t sample_rate, + uint8_t channel_count) { + haltest_info("(sink) %s: addr=%s\n sample_rate=%d\n channel_count=%d\n", + __func__, bt_bdaddr_t2str(bd_addr, last_addr), + sample_rate, channel_count); +} + +static btav_callbacks_t av_cbacks = { + .size = sizeof(av_cbacks), + .connection_state_cb = connection_state, + .audio_state_cb = audio_state, + .audio_config_cb = audio_config, +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_av_sink); + + EXEC(if_av_sink->init, &av_cbacks); +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_av_sink); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_av_sink->connect, &addr); +} + +/* disconnect */ + +static void disconnect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +static void disconnect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_av_sink); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_av_sink->disconnect, &addr); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_av_sink); + + EXECV(if_av_sink->cleanup); + if_av_sink = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(connect, "<addr>"), + STD_METHODCH(disconnect, "<addr>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface av_sink_if = { + .name = "av-sink", + .methods = methods +};
diff --git a/android/client/if-av.c b/android/client/if-av.c new file mode 100644 index 0000000..85c641b --- /dev/null +++ b/android/client/if-av.c
@@ -0,0 +1,143 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include "if-main.h" +#include "../hal-utils.h" + +const btav_interface_t *if_av = NULL; + +SINTMAP(btav_connection_state_t, -1, "(unknown)") + DELEMENT(BTAV_CONNECTION_STATE_DISCONNECTED), + DELEMENT(BTAV_CONNECTION_STATE_CONNECTING), + DELEMENT(BTAV_CONNECTION_STATE_CONNECTED), + DELEMENT(BTAV_CONNECTION_STATE_DISCONNECTING), +ENDMAP + +SINTMAP(btav_audio_state_t, -1, "(unknown)") + DELEMENT(BTAV_AUDIO_STATE_REMOTE_SUSPEND), + DELEMENT(BTAV_AUDIO_STATE_STOPPED), + DELEMENT(BTAV_AUDIO_STATE_STARTED), +ENDMAP + +static char last_addr[MAX_ADDR_STR_LEN]; + +static void connection_state(btav_connection_state_t state, + bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: connection_state=%s remote_bd_addr=%s\n", __func__, + btav_connection_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +static void audio_state(btav_audio_state_t state, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: audio_state=%s remote_bd_addr=%s\n", __func__, + btav_audio_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void audio_config(bt_bdaddr_t *bd_addr, uint32_t sample_rate, + uint8_t channel_count) { + haltest_info("%s: remote_addr=%s\n sample_rate=%d\n channel_count=%d\n", + __func__, bt_bdaddr_t2str(bd_addr, last_addr), + sample_rate, channel_count); +} +#endif + +static btav_callbacks_t av_cbacks = { + .size = sizeof(av_cbacks), + .connection_state_cb = connection_state, + .audio_state_cb = audio_state, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .audio_config_cb = audio_config, +#endif +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_av); + + EXEC(if_av->init, &av_cbacks); +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_av); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_av->connect, &addr); +} + +/* disconnect */ + +static void disconnect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +static void disconnect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_av); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_av->disconnect, &addr); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_av); + + EXECV(if_av->cleanup); + if_av = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(connect, "<addr>"), + STD_METHODCH(disconnect, "<addr>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface av_if = { + .name = "av", + .methods = methods +};
diff --git a/android/client/if-bt.c b/android/client/if-bt.c new file mode 100644 index 0000000..c9acf6c --- /dev/null +++ b/android/client/if-bt.c
@@ -0,0 +1,1023 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <string.h> +#include <inttypes.h> + +#include "if-main.h" +#include "terminal.h" +#include "../hal-msg.h" +#include "../hal-utils.h" + +static hw_device_t *bt_device; +const bt_interface_t *if_bluetooth; + +#define VERIFY_PROP_TYPE_ARG(n, typ) \ + do { \ + if (n < argc) \ + typ = str2btpropertytype(argv[n]); \ + else { \ + haltest_error("No property type specified\n"); \ + return;\ + } \ + } while (0) + +static bt_scan_mode_t str2btscanmode(const char *str) +{ + bt_scan_mode_t v = str2bt_scan_mode_t(str); + + if ((int) v != -1) + return v; + + haltest_warn("WARN: %s cannot convert %s\n", __func__, str); + return (bt_scan_mode_t) atoi(str); +} + +static bt_ssp_variant_t str2btsspvariant(const char *str) +{ + bt_ssp_variant_t v = str2bt_ssp_variant_t(str); + + if ((int) v != -1) + return v; + + haltest_warn("WARN: %s cannot convert %s\n", __func__, str); + return (bt_ssp_variant_t) atoi(str); +} + +static bt_property_type_t str2btpropertytype(const char *str) +{ + bt_property_type_t v = str2bt_property_type_t(str); + + if ((int) v != -1) + return v; + + haltest_warn("WARN: %s cannot convert %s\n", __func__, str); + return (bt_property_type_t) atoi(str); +} + +static void dump_properties(int num_properties, bt_property_t *properties) +{ + int i; + + for (i = 0; i < num_properties; i++) { + /* + * properities sometimes come unaligned hence memcp to + * aligned buffer + */ + bt_property_t prop; + memcpy(&prop, properties + i, sizeof(prop)); + + haltest_info("prop: %s\n", btproperty2str(&prop)); + } +} + +/* Cache for remote devices, stored in sorted array */ +static bt_bdaddr_t *remote_devices = NULL; +static int remote_devices_cnt = 0; +static int remote_devices_capacity = 0; + +/* Adds address to remote device set so it can be used in tab completion */ +void add_remote_device(const bt_bdaddr_t *addr) +{ + int i; + + if (remote_devices == NULL) { + remote_devices = malloc(4 * sizeof(bt_bdaddr_t)); + remote_devices_cnt = 0; + if (remote_devices == NULL) { + remote_devices_capacity = 0; + return; + } + + remote_devices_capacity = 4; + } + + /* Array is sorted, search for right place */ + for (i = 0; i < remote_devices_cnt; ++i) { + int res = memcmp(&remote_devices[i], addr, sizeof(*addr)); + + if (res == 0) + return; /* Already added */ + else if (res > 0) + break; + } + + /* Realloc space if needed */ + if (remote_devices_cnt >= remote_devices_capacity) { + bt_bdaddr_t *tmp; + + remote_devices_capacity *= 2; + /* + * Save reference to previously allocated memory block so that + * it can be freed in case realloc fails. + */ + tmp = remote_devices; + + remote_devices = realloc(remote_devices, sizeof(bt_bdaddr_t) * + remote_devices_capacity); + if (remote_devices == NULL) { + free(tmp); + remote_devices_capacity = 0; + remote_devices_cnt = 0; + return; + } + } + + if (i < remote_devices_cnt) + memmove(remote_devices + i + 1, remote_devices + i, + (remote_devices_cnt - i) * sizeof(bt_bdaddr_t)); + remote_devices[i] = *addr; + remote_devices_cnt++; +} + +const char *enum_devices(void *v, int i) +{ + static char buf[MAX_ADDR_STR_LEN]; + + if (i >= remote_devices_cnt) + return NULL; + + bt_bdaddr_t2str(&remote_devices[i], buf); + return buf; +} + +static void add_remote_device_from_props(int num_properties, + const bt_property_t *properties) +{ + int i; + + for (i = 0; i < num_properties; i++) { + /* + * properities sometimes come unaligned hence memcp to + * aligned buffer + */ + bt_property_t property; + + memcpy(&property, properties + i, sizeof(property)); + if (property.type == BT_PROPERTY_BDADDR) + add_remote_device((bt_bdaddr_t *) property.val); + } +} + +bool close_hw_bt_dev(void) +{ + if (!bt_device) + return false; + + bt_device->close(bt_device); + return true; +} + +static void adapter_state_changed_cb(bt_state_t state) +{ + haltest_info("%s: state=%s\n", __func__, bt_state_t2str(state)); +} + +static void adapter_properties_cb(bt_status_t status, int num_properties, + bt_property_t *properties) +{ + haltest_info("%s: status=%s num_properties=%d\n", __func__, + bt_status_t2str(status), num_properties); + + dump_properties(num_properties, properties); +} + +static void remote_device_properties_cb(bt_status_t status, + bt_bdaddr_t *bd_addr, + int num_properties, + bt_property_t *properties) +{ + haltest_info("%s: status=%s bd_addr=%s num_properties=%d\n", __func__, + bt_status_t2str(status), bdaddr2str(bd_addr), + num_properties); + + add_remote_device(bd_addr); + + dump_properties(num_properties, properties); +} + +static void device_found_cb(int num_properties, bt_property_t *properties) +{ + haltest_info("%s: num_properties=%d\n", __func__, num_properties); + + add_remote_device_from_props(num_properties, properties); + + dump_properties(num_properties, properties); +} + +static void discovery_state_changed_cb(bt_discovery_state_t state) +{ + haltest_info("%s: state=%s\n", __func__, + bt_discovery_state_t2str(state)); +} + +/* + * Buffer for remote addres that came from one of bind request. + * It's stored for command completion. + */ +static char last_remote_addr[MAX_ADDR_STR_LEN]; +static bt_ssp_variant_t last_ssp_variant = (bt_ssp_variant_t) -1; + +static bt_bdaddr_t pin_request_addr; +static void pin_request_answer(char *reply) +{ + bt_pin_code_t pin; + int accept = 0; + int pin_len = strlen(reply); + + if (pin_len > 0) { + accept = 1; + if (pin_len > 16) + pin_len = 16; + memcpy(&pin.pin, reply, pin_len); + } + + EXEC(if_bluetooth->pin_reply, &pin_request_addr, accept, pin_len, &pin); +} + +static void pin_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod) +{ + /* Store for command completion */ + bt_bdaddr_t2str(remote_bd_addr, last_remote_addr); + pin_request_addr = *remote_bd_addr; + + haltest_info("%s: remote_bd_addr=%s bd_name=%s cod=%06x\n", __func__, + last_remote_addr, bd_name->name, cod); + terminal_prompt_for("Enter pin: ", pin_request_answer); +} + +/* Variables to store information from ssp_request_cb used for ssp_reply */ +static bt_bdaddr_t ssp_request_addr; +static bt_ssp_variant_t ssp_request_variant; +static uint32_t ssp_request_pask_key; + +/* Called when user hit enter on prompt for confirmation */ +static void ssp_request_yes_no_answer(char *reply) +{ + int accept = *reply == 0 || *reply == 'y' || *reply == 'Y'; + + if_bluetooth->ssp_reply(&ssp_request_addr, ssp_request_variant, accept, + ssp_request_pask_key); +} + +static void ssp_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod, bt_ssp_variant_t pairing_variant, + uint32_t pass_key) +{ + static char prompt[50]; + + /* Store for command completion */ + bt_bdaddr_t2str(remote_bd_addr, last_remote_addr); + last_ssp_variant = pairing_variant; + + haltest_info("%s: remote_bd_addr=%s bd_name=%s cod=%06x pairing_variant=%s pass_key=%d\n", + __func__, last_remote_addr, bd_name->name, cod, + bt_ssp_variant_t2str(pairing_variant), pass_key); + + switch (pairing_variant) { + case BT_SSP_VARIANT_PASSKEY_CONFIRMATION: + sprintf(prompt, "Does other device show %d [Y/n] ?", pass_key); + + ssp_request_addr = *remote_bd_addr; + ssp_request_variant = pairing_variant; + ssp_request_pask_key = pass_key; + + terminal_prompt_for(prompt, ssp_request_yes_no_answer); + break; + case BT_SSP_VARIANT_CONSENT: + sprintf(prompt, "Consent pairing [Y/n] ?"); + + ssp_request_addr = *remote_bd_addr; + ssp_request_variant = pairing_variant; + ssp_request_pask_key = 0; + + terminal_prompt_for(prompt, ssp_request_yes_no_answer); + break; + case BT_SSP_VARIANT_PASSKEY_ENTRY: + case BT_SSP_VARIANT_PASSKEY_NOTIFICATION: + default: + haltest_info("Not automatically handled\n"); + break; + } +} + +static void bond_state_changed_cb(bt_status_t status, + bt_bdaddr_t *remote_bd_addr, + bt_bond_state_t state) +{ + haltest_info("%s: status=%s remote_bd_addr=%s state=%s\n", __func__, + bt_status_t2str(status), bdaddr2str(remote_bd_addr), + bt_bond_state_t2str(state)); +} + +static void acl_state_changed_cb(bt_status_t status, + bt_bdaddr_t *remote_bd_addr, + bt_acl_state_t state) +{ + haltest_info("%s: status=%s remote_bd_addr=%s state=%s\n", __func__, + bt_status_t2str(status), bdaddr2str(remote_bd_addr), + bt_acl_state_t2str(state)); +} + +static void thread_evt_cb(bt_cb_thread_evt evt) +{ + haltest_info("%s: evt=%s\n", __func__, bt_cb_thread_evt2str(evt)); +} + +static void dut_mode_recv_cb(uint16_t opcode, uint8_t *buf, uint8_t len) +{ + haltest_info("%s\n", __func__); +} + +static void le_test_mode_cb(bt_status_t status, uint16_t num_packets) +{ + haltest_info("%s %s %d\n", __func__, bt_status_t2str(status), + num_packets); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void energy_info_cb(bt_activity_energy_info *energy_info) +{ + haltest_info("%s status=%s, ctrl_state=0x%02X, tx_time=0x%jx," + "rx_time=0x%jx, idle_time=0x%jx, energu_used=0x%jx\n", + __func__, bt_status_t2str(energy_info->status), + energy_info->ctrl_state, energy_info->tx_time, + energy_info->rx_time, energy_info->idle_time, + energy_info->energy_used); +} +#endif + +static bt_callbacks_t bt_callbacks = { + .size = sizeof(bt_callbacks), + .adapter_state_changed_cb = adapter_state_changed_cb, + .adapter_properties_cb = adapter_properties_cb, + .remote_device_properties_cb = remote_device_properties_cb, + .device_found_cb = device_found_cb, + .discovery_state_changed_cb = discovery_state_changed_cb, + .pin_request_cb = pin_request_cb, + .ssp_request_cb = ssp_request_cb, + .bond_state_changed_cb = bond_state_changed_cb, + .acl_state_changed_cb = acl_state_changed_cb, + .thread_evt_cb = thread_evt_cb, + .dut_mode_recv_cb = dut_mode_recv_cb, + .le_test_mode_cb = le_test_mode_cb, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .energy_info_cb = energy_info_cb, +#endif +}; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static alarm_cb alarm_cb_p = NULL; +static void *alarm_cb_p_data = NULL; + +static bool set_wake_alarm(uint64_t delay_millis, bool should_wake, alarm_cb cb, + void *data) +{ + haltest_info("%s: delay %"PRIu64" should_wake %u cb %p data %p\n", + __func__, delay_millis, should_wake, cb, data); + + /* TODO call alarm callback after specified delay */ + alarm_cb_p = cb; + alarm_cb_p_data = data; + + return true; +} + +static int acquire_wake_lock(const char *lock_name) +{ + haltest_info("%s: %s\n", __func__, lock_name); + + return BT_STATUS_SUCCESS; +} + +static int release_wake_lock(const char *lock_name) +{ + haltest_info("%s: %s\n", __func__, lock_name); + + return BT_STATUS_SUCCESS; +} + +static bt_os_callouts_t bt_os_callouts = { + .size = sizeof(bt_os_callouts), + .set_wake_alarm = set_wake_alarm, + .acquire_wake_lock = acquire_wake_lock, + .release_wake_lock = release_wake_lock, +}; +#endif + +static void init_p(int argc, const char **argv) +{ + int err; + const hw_module_t *module; + + err = hw_get_module(BT_HARDWARE_MODULE_ID, &module); + if (err) { + haltest_error("he_get_module returned %d\n", err); + return; + } + + err = module->methods->open(module, BT_HARDWARE_MODULE_ID, &bt_device); + if (err) { + haltest_error("module->methods->open returned %d\n", err); + return; + } + + if_bluetooth = + ((bluetooth_device_t *) bt_device)->get_bluetooth_interface(); + if (!if_bluetooth) { + haltest_error("get_bluetooth_interface returned NULL\n"); + return; + } + + EXEC(if_bluetooth->init, &bt_callbacks); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + EXEC(if_bluetooth->set_os_callouts, &bt_os_callouts); +#endif +} + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXECV(if_bluetooth->cleanup); + + if_bluetooth = NULL; +} + +static void enable_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXEC(if_bluetooth->enable); +} +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void read_energy_info_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXEC(if_bluetooth->read_energy_info); +} + +#define get_connection_state_c complete_addr_c + +static void get_connection_state_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + + VERIFY_ADDR_ARG(2, &addr); + + haltest_info("if_bluetooth->get_connection_state : %d\n", + if_bluetooth->get_connection_state(&addr)); +} +#endif + +static void disable_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXEC(if_bluetooth->disable); +} + +static void get_adapter_properties_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXEC(if_bluetooth->get_adapter_properties); +} + +static void get_adapter_property_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(bt_property_type_t); + *enum_func = enum_defines; + } +} + +static void get_adapter_property_p(int argc, const char **argv) +{ + int type; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_PROP_TYPE_ARG(2, type); + + EXEC(if_bluetooth->get_adapter_property, type); +} + +static const char * const names[] = { + "BT_PROPERTY_BDNAME", + "BT_PROPERTY_ADAPTER_SCAN_MODE", + "BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT", + NULL +}; + +static void set_adapter_property_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = (void *) names; + *enum_func = enum_strings; + } else if (argc == 4) { + if (0 == strcmp(argv[2], "BT_PROPERTY_ADAPTER_SCAN_MODE")) { + *user = TYPE_ENUM(bt_scan_mode_t); + *enum_func = enum_defines; + } + } +} + +static void set_adapter_property_p(int argc, const char **argv) +{ + bt_property_t property; + bt_scan_mode_t mode; + int timeout; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_PROP_TYPE_ARG(2, property.type); + + if (argc <= 3) { + haltest_error("No property value specified\n"); + return; + } + switch (property.type) { + case BT_PROPERTY_BDNAME: + property.len = strlen(argv[3]) + 1; + property.val = (char *) argv[3]; + break; + + case BT_PROPERTY_ADAPTER_SCAN_MODE: + mode = str2btscanmode(argv[3]); + property.len = sizeof(bt_scan_mode_t); + property.val = &mode; + break; + + case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT: + timeout = atoi(argv[3]); + property.val = &timeout; + property.len = sizeof(timeout); + break; + + case BT_PROPERTY_BDADDR: + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_CLASS_OF_DEVICE: + case BT_PROPERTY_TYPE_OF_DEVICE: + case BT_PROPERTY_SERVICE_RECORD: + case BT_PROPERTY_ADAPTER_BONDED_DEVICES: + case BT_PROPERTY_REMOTE_FRIENDLY_NAME: + case BT_PROPERTY_REMOTE_RSSI: + case BT_PROPERTY_REMOTE_VERSION_INFO: + case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP: +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + case BT_PROPERTY_LOCAL_LE_FEATURES: +#endif + default: + haltest_error("Invalid property %s\n", argv[3]); + return; + } + + EXEC(if_bluetooth->set_adapter_property, &property); +} + +/* This function is to be used for completion methods that need only address */ +static void complete_addr_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +/* Just addres to complete, use complete_addr_c */ +#define get_remote_device_properties_c complete_addr_c + +static void get_remote_device_properties_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_bluetooth->get_remote_device_properties, &addr); +} + +static void get_remote_device_property_c(int argc, const char **argv, + enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } else if (argc == 4) { + *user = TYPE_ENUM(bt_property_type_t); + *enum_func = enum_defines; + } +} + +static void get_remote_device_property_p(int argc, const char **argv) +{ + bt_property_type_t type; + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + VERIFY_PROP_TYPE_ARG(3, type); + + EXEC(if_bluetooth->get_remote_device_property, &addr, type); +} + +/* + * Same completion as for get_remote_device_property_c can be used for + * set_remote_device_property_c. No need to create separate function. + */ +#define set_remote_device_property_c get_remote_device_property_c + +static void set_remote_device_property_p(int argc, const char **argv) +{ + bt_property_t property; + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + VERIFY_PROP_TYPE_ARG(3, property.type); + + switch (property.type) { + case BT_PROPERTY_REMOTE_FRIENDLY_NAME: + property.len = strlen(argv[4]); + property.val = (char *) argv[4]; + break; + case BT_PROPERTY_BDNAME: + case BT_PROPERTY_BDADDR: + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_CLASS_OF_DEVICE: + case BT_PROPERTY_TYPE_OF_DEVICE: + case BT_PROPERTY_SERVICE_RECORD: + case BT_PROPERTY_ADAPTER_SCAN_MODE: + case BT_PROPERTY_ADAPTER_BONDED_DEVICES: + case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT: + case BT_PROPERTY_REMOTE_RSSI: + case BT_PROPERTY_REMOTE_VERSION_INFO: + case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP: +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + case BT_PROPERTY_LOCAL_LE_FEATURES: +#endif + default: + return; + } + + EXEC(if_bluetooth->set_remote_device_property, &addr, &property); +} + +/* For now uuid is not autocompleted. Use routine for complete_addr_c */ +#define get_remote_service_record_c complete_addr_c + +static void get_remote_service_record_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bt_uuid_t uuid; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + if (argc <= 3) { + haltest_error("No uuid specified\n"); + return; + } + + str2bt_uuid_t(argv[3], &uuid); + + EXEC(if_bluetooth->get_remote_service_record, &addr, &uuid); +} + +/* Just addres to complete, use complete_addr_c */ +#define get_remote_services_c complete_addr_c + +static void get_remote_services_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_bluetooth->get_remote_services, &addr); +} + +static void start_discovery_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXEC(if_bluetooth->start_discovery); +} + +static void cancel_discovery_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_bluetooth); + + EXEC(if_bluetooth->cancel_discovery); +} + +/* Just addres to complete, use complete_addr_c */ +#define create_bond_c complete_addr_c + +static void create_bond_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + int transport; +#endif + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + if (argc < 4) + transport = BT_TRANSPORT_UNKNOWN; + else + transport = atoi(argv[3]); + + EXEC(if_bluetooth->create_bond, &addr, transport); +#else + EXEC(if_bluetooth->create_bond, &addr); +#endif +} + +/* Just addres to complete, use complete_addr_c */ +#define remove_bond_c complete_addr_c + +static void remove_bond_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_bluetooth->remove_bond, &addr); +} + +/* Just addres to complete, use complete_addr_c */ +#define cancel_bond_c complete_addr_c + +static void cancel_bond_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_bluetooth->cancel_bond, &addr); +} + +static void pin_reply_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + static const char *const completions[] = { last_remote_addr, NULL }; + + if (argc == 3) { + *user = (void *) completions; + *enum_func = enum_strings; + } +} + +static void pin_reply_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bt_pin_code_t pin; + int pin_len = 0; + int accept = 0; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + if (argc > 3) { + accept = 1; + pin_len = strlen(argv[3]); + memcpy(pin.pin, argv[3], pin_len); + } + + EXEC(if_bluetooth->pin_reply, &addr, accept, pin_len, &pin); +} + +static void ssp_reply_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = last_remote_addr; + *enum_func = enum_one_string; + } else if (argc == 5) { + *user = "1"; + *enum_func = enum_one_string; + } else if (argc == 4) { + if (-1 != (int) last_ssp_variant) { + *user = (void *) bt_ssp_variant_t2str(last_ssp_variant); + *enum_func = enum_one_string; + } else { + *user = TYPE_ENUM(bt_ssp_variant_t); + *enum_func = enum_defines; + } + } +} + +static void ssp_reply_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bt_ssp_variant_t var; + int accept; + int passkey; + + RETURN_IF_NULL(if_bluetooth); + VERIFY_ADDR_ARG(2, &addr); + + if (argc < 4) { + haltest_error("No ssp variant specified\n"); + return; + } + + var = str2btsspvariant(argv[3]); + if (argc < 5) { + haltest_error("No accept value specified\n"); + return; + } + + accept = atoi(argv[4]); + passkey = 0; + + if (accept && var == BT_SSP_VARIANT_PASSKEY_ENTRY && argc >= 5) + passkey = atoi(argv[4]); + + EXEC(if_bluetooth->ssp_reply, &addr, var, accept, passkey); +} + +static void get_profile_interface_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + static const char *const profile_ids[] = { + BT_PROFILE_HANDSFREE_ID, + BT_PROFILE_ADVANCED_AUDIO_ID, + BT_PROFILE_HEALTH_ID, + BT_PROFILE_SOCKETS_ID, + BT_PROFILE_HIDHOST_ID, + BT_PROFILE_PAN_ID, + BT_PROFILE_GATT_ID, + BT_PROFILE_AV_RC_ID, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + BT_PROFILE_HANDSFREE_CLIENT_ID, + BT_PROFILE_MAP_CLIENT_ID, + BT_PROFILE_AV_RC_CTRL_ID, + BT_PROFILE_ADVANCED_AUDIO_SINK_ID, +#endif + NULL + }; + + if (argc == 3) { + *user = (void *) profile_ids; + *enum_func = enum_strings; + } +} + +static void get_profile_interface_p(int argc, const char **argv) +{ + const char *id; + const void **pif = NULL; + + RETURN_IF_NULL(if_bluetooth); + if (argc <= 2) { + haltest_error("No interface specified\n"); + return; + } + + id = argv[2]; + + if (strcmp(BT_PROFILE_HANDSFREE_ID, id) == 0) + pif = (const void **) &if_hf; + else if (strcmp(BT_PROFILE_ADVANCED_AUDIO_ID, id) == 0) + pif = (const void **) &if_av; + else if (strcmp(BT_PROFILE_HEALTH_ID, id) == 0) + pif = (const void **) &if_hl; + else if (strcmp(BT_PROFILE_SOCKETS_ID, id) == 0) + pif = (const void **) &if_sock; + else if (strcmp(BT_PROFILE_HIDHOST_ID, id) == 0) + pif = (const void **) &if_hh; + else if (strcmp(BT_PROFILE_PAN_ID, id) == 0) + pif = (const void **) &if_pan; + else if (strcmp(BT_PROFILE_AV_RC_ID, id) == 0) + pif = (const void **) &if_rc; + else if (strcmp(BT_PROFILE_GATT_ID, id) == 0) + pif = (const void **) &if_gatt; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + else if (strcmp(BT_PROFILE_AV_RC_CTRL_ID, id) == 0) + pif = (const void **) &if_rc_ctrl; + else if (strcmp(BT_PROFILE_HANDSFREE_CLIENT_ID, id) == 0) + pif = (const void **) &if_hf_client; + else if (strcmp(BT_PROFILE_MAP_CLIENT_ID, id) == 0) + pif = (const void **) &if_mce; + else if (strcmp(BT_PROFILE_ADVANCED_AUDIO_SINK_ID, id) == 0) + pif = (const void **) &if_av_sink; +#endif + else + haltest_error("%s is not correct for get_profile_interface\n", + id); + + if (pif != NULL) { + *pif = if_bluetooth->get_profile_interface(id); + haltest_info("get_profile_interface(%s) : %p\n", id, *pif); + } +} + +static void dut_mode_configure_p(int argc, const char **argv) +{ + uint8_t mode; + + RETURN_IF_NULL(if_bluetooth); + + if (argc <= 2) { + haltest_error("No dut mode specified\n"); + return; + } + + mode = strtol(argv[2], NULL, 0); + + EXEC(if_bluetooth->dut_mode_configure, mode); +} + +static void dut_mode_send_p(int argc, const char **argv) +{ + haltest_error("not implemented\n"); +} + +static void le_test_mode_p(int argc, const char **argv) +{ + haltest_error("not implemented\n"); +} + +static void config_hci_snoop_log_p(int argc, const char **argv) +{ + uint8_t mode; + + RETURN_IF_NULL(if_bluetooth); + + if (argc <= 2) { + haltest_error("No mode specified\n"); + return; + } + + mode = strtol(argv[2], NULL, 0); + + EXEC(if_bluetooth->config_hci_snoop_log, mode); +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHOD(cleanup), + STD_METHOD(enable), + STD_METHOD(disable), + STD_METHOD(get_adapter_properties), + STD_METHODCH(get_adapter_property, "<prop_type>"), + STD_METHODCH(set_adapter_property, "<prop_type> <prop_value>"), + STD_METHODCH(get_remote_device_properties, "<addr>"), + STD_METHODCH(get_remote_device_property, "<addr> <property_type>"), + STD_METHODCH(set_remote_device_property, + "<addr> <property_type> <value>"), + STD_METHODCH(get_remote_service_record, "<addr> <uuid>"), + STD_METHODCH(get_remote_services, "<addr>"), + STD_METHOD(start_discovery), + STD_METHOD(cancel_discovery), +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + STD_METHODCH(create_bond, "<addr> [<transport>]"), + STD_METHOD(read_energy_info), + STD_METHODCH(get_connection_state, "<addr>"), +#else + STD_METHODCH(create_bond, "<addr>"), +#endif + STD_METHODCH(remove_bond, "<addr>"), + STD_METHODCH(cancel_bond, "<addr>"), + STD_METHODCH(pin_reply, "<address> [<pin>]"), + STD_METHODCH(ssp_reply, "<address> <ssp_veriant> 1|0 [<passkey>]"), + STD_METHODCH(get_profile_interface, "<profile id>"), + STD_METHODH(dut_mode_configure, "<dut mode>"), + STD_METHOD(dut_mode_send), + STD_METHOD(le_test_mode), + STD_METHODH(config_hci_snoop_log, "<mode>"), + END_METHOD +}; + +const struct interface bluetooth_if = { + .name = "bluetooth", + .methods = methods +};
diff --git a/android/client/if-gatt.c b/android/client/if-gatt.c new file mode 100644 index 0000000..3526783 --- /dev/null +++ b/android/client/if-gatt.c
@@ -0,0 +1,2675 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> + +#include <string.h> + +#include <hardware/bluetooth.h> + +#include "../hal-utils.h" +#include "if-main.h" + +const btgatt_interface_t *if_gatt = NULL; + +/* + * In version 19 some callback were changed. + * btgatt_char_id_t -> btgatt_gatt_id_t + * bt_uuid_t -> btgatt_gatt_id_t + */ +#define str2btgatt_descr_id_t str2btgatt_gatt_id_t +#define btgatt_descr_id_t2str btgatt_gatt_id_t2str +#define btgatt_descr_id_t btgatt_gatt_id_t + +#define MAX_CHAR_ID_STR_LEN (MAX_UUID_STR_LEN + 3 + 11) +#define MAX_SRVC_ID_STR_LEN (MAX_UUID_STR_LEN + 3 + 11 + 1 + 11) +/* How man characters print from binary objects (arbitrary) */ +#define MAX_HEX_VAL_STR_LEN 100 +#define MAX_NOTIFY_PARAMS_STR_LEN (MAX_SRVC_ID_STR_LEN + MAX_CHAR_ID_STR_LEN \ + + MAX_ADDR_STR_LEN + MAX_HEX_VAL_STR_LEN + 60) +#define MAX_READ_PARAMS_STR_LEN (MAX_SRVC_ID_STR_LEN + MAX_CHAR_ID_STR_LEN \ + + MAX_UUID_STR_LEN + MAX_HEX_VAL_STR_LEN + 80) + +/* Hex arguments must have "0x" or "0X" prefix */ +#define VERIFY_INT_ARG(n, v, err) \ + do { \ + if (n < argc) \ + v = strtol(argv[n], NULL, 0); \ + else { \ + haltest_error(err); \ + return;\ + } \ + } while (0) + +#define VERIFY_HEX_ARG(n, v, err) \ + do { \ + if (n < argc) \ + v = strtol(argv[n], NULL, 16); \ + else { \ + haltest_error(err); \ + return;\ + } \ + } while (0) + +/* Helper macros to verify arguments of methods */ +#define VERIFY_CLIENT_IF(n, v) VERIFY_INT_ARG(n, v, "No client_if specified\n") +#define VERIFY_SERVER_IF(n, v) VERIFY_INT_ARG(n, v, "No server_if specified\n") +#define VERIFY_CONN_ID(n, v) VERIFY_INT_ARG(n, v, "No conn_if specified\n") +#define VERIFY_TRANS_ID(n, v) VERIFY_INT_ARG(n, v, "No trans_id specified\n") +#define VERIFY_STATUS(n, v) VERIFY_INT_ARG(n, v, "No status specified\n") +#define VERIFY_OFFSET(n, v) VERIFY_INT_ARG(n, v, "No offset specified\n") +#define VERIFY_TEST_ARG(n, v) VERIFY_INT_ARG(n, v, "No test arg specified\n") +#define VERIFY_ACTION(n, v) VERIFY_INT_ARG(n, v, "No action specified\n") +#define VERIFY_FILT_TYPE(n, v) VERIFY_INT_ARG(n, v, "No filter specified\n") +#define VERIFY_FILT_INDEX(n, v) VERIFY_INT_ARG(n, v, \ + "No filter index specified\n") +#define VERIFY_FEAT_SELN(n, v) VERIFY_INT_ARG(n, v, "No feat seln specified\n") +#define VERIFY_LIST_LOGIC_TYPE(n, v) VERIFY_INT_ARG(n, v, \ + "No list logic type specified\n") +#define VERIFY_FILT_LOGIC_TYPE(n, v) VERIFY_INT_ARG(n, v, \ + "No filt logic type specified\n") +#define VERIFY_RSSI_HI_THR(n, v) VERIFY_INT_ARG(n, v, \ + "No rssi hi threshold specified\n") +#define VERIFY_RSSI_LOW_THR(n, v) VERIFY_INT_ARG(n, v, \ + "No low threshold specified\n") +#define VERIFY_DELY_MODE(n, v) VERIFY_INT_ARG(n, v, "No delay mode specified\n") +#define VERIFY_FND_TIME(n, v) VERIFY_INT_ARG(n, v, "No found time specified\n") +#define VERIFY_LOST_TIME(n, v) VERIFY_INT_ARG(n, v, "No lost time specified\n") +#define VERIFY_FND_TIME_CNT(n, v) VERIFY_INT_ARG(n, v, \ + "No found timer counter specified\n") +#define VERIFY_COMP_ID(n, v) VERIFY_INT_ARG(n, v, "No company id specified\n") +#define VERIFY_COMP_ID_MASK(n, v) VERIFY_INT_ARG(n, v, \ + "No comp. id mask specified\n") +#define VERIFY_DATA_LEN(n, v) VERIFY_INT_ARG(n, v, "No data len specified\n") +#define VERIFY_MASK_LEN(n, v) VERIFY_INT_ARG(n, v, "No mask len specified\n") +#define VERIFY_MIN_INTERVAL(n, v) VERIFY_INT_ARG(n, v, \ + "No min interval specified\n") +#define VERIFY_MAX_INTERVAL(n, v) VERIFY_INT_ARG(n, v, \ + "No max interval specified\n") +#define VERIFY_APPEARANCE(n, v) VERIFY_INT_ARG(n, v, "No apperance specified\n") +#define VERIFY_MANUFACTURER_LEN(n, v) VERIFY_INT_ARG(n, v, \ + "No manufacturer len specified\n") +#define VERIFY_SERVICE_DATA_LEN(n, v) VERIFY_INT_ARG(n, v, \ + "No service data len specified\n") +#define VERIFY_SERVICE_UUID_LEN(n, v) VERIFY_INT_ARG(n, v, \ + "No service uuid len specified\n") +#define VERIFY_MTU(n, v) VERIFY_INT_ARG(n, v, "No mtu specified\n") +#define VERIFY_LATENCY(n, v) VERIFY_INT_ARG(n, v, \ + "No latency specified\n") +#define VERIFY_TIMEOUT(n, v) VERIFY_INT_ARG(n, v, "No timeout specified\n") +#define VERIFY_SCAN_INTERVAL(n, v) VERIFY_INT_ARG(n, v, \ + "No scan interval specified\n") +#define VERIFY_SCAN_WINDOW(n, v) VERIFY_INT_ARG(n, v, \ + "No scan window specified\n") +#define VERIFY_ADV_TYPE(n, v) VERIFY_INT_ARG(n, v, \ + "No advertising type specified\n") +#define VERIFY_CHNL_MAP(n, v) VERIFY_INT_ARG(n, v, \ + "No channel map specified\n") +#define VERIFY_TX_POWER(n, v) VERIFY_INT_ARG(n, v, \ + "No tx power specified\n") +#define VERIFY_TIMEOUT_S(n, v) VERIFY_INT_ARG(n, v, \ + "No timeout in sec specified\n") +#define VERIFY_BATCH_SCAN_FULL_MAX(n, v) VERIFY_INT_ARG(n, v, \ + "No batch scan full max specified\n") +#define VERIFY_BATCH_SCAN_TRUNC_MAX(n, v) VERIFY_INT_ARG(n, v, \ + "No batch scan trunc max specified\n") +#define VERIFY_BATCH_SCAN_NOTIFY_THR(n, v) VERIFY_INT_ARG(n, v, \ + "No batch scan notify threshold specified\n") +#define VERIFY_SCAN_MODE(n, v) VERIFY_INT_ARG(n, v, \ + "No scan mode specified\n") +#define VERIFY_ADDR_TYPE(n, v) VERIFY_INT_ARG(n, v, \ + "No address type specified\n") +#define VERIFY_DISCARD_RULE(n, v) VERIFY_INT_ARG(n, v, \ + "No discard rule specified\n") +#define VERIFY_HANDLE(n, v) VERIFY_HEX_ARG(n, v, "No "#v" specified\n") +#define VERIFY_SERVICE_HANDLE(n, v) VERIFY_HANDLE(n, v) + +#define VERIFY_UUID(n, v) \ + do { \ + if (n < argc) \ + gatt_str2bt_uuid_t(argv[n], -1, v); \ + else { \ + haltest_error("No uuid specified\n"); \ + return;\ + } \ + } while (0) + +#define VERIFY_SRVC_ID(n, v) \ + do { \ + if (n < argc) \ + str2btgatt_srvc_id_t(argv[n], v); \ + else { \ + haltest_error("No srvc_id specified\n"); \ + return;\ + } \ + } while (0) + +#define VERIFY_CHAR_ID(n, v) \ + do { \ + if (n < argc) \ + str2btgatt_gatt_id_t(argv[n], v); \ + else { \ + haltest_error("No char_id specified\n"); \ + return;\ + } \ + } while (0) + +#define VERIFY_DESCR_ID(n, v) \ + do { \ + if (n < argc) \ + str2btgatt_descr_id_t(argv[n], v); \ + else { \ + haltest_error("No descr_id specified\n"); \ + return;\ + } \ + } while (0) + +#define GET_VERIFY_HEX_STRING(i, v, l) \ + do { \ + int ll;\ + const char *n = argv[i]; \ + if (argc <= i) { \ + haltest_error("No value specified\n"); \ + return; \ + } \ + if (n[0] != '0' || (n[1] != 'X' && n[1] != 'x')) { \ + haltest_error("Value must be hex string\n"); \ + return; \ + } \ + ll = fill_buffer(n + 2, (uint8_t *) v, sizeof(v)); \ + if (ll < 0) { \ + haltest_error("Value must be byte hex string\n"); \ + return; \ + } \ + l = ll; \ + } while (0) + +/* Gatt uses little endian uuid */ +static const char GATT_BASE_UUID[] = { + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * converts gatt uuid to string + * buf should be at least 39 bytes + * + * This function formats 16, 32 and 128 bits uuid + * + * returns string representation of uuid + */ +static char *gatt_uuid_t2str(const bt_uuid_t *uuid, char *buf) +{ + int shift = 0; + int i = 16; + int limit = 0; + int j = 0; + + /* for bluetooth uuid only 32 bits */ + if (0 == memcmp(&uuid->uu, &GATT_BASE_UUID, + sizeof(bt_uuid_t) - 4)) { + limit = 12; + /* make it 16 bits */ + if (uuid->uu[15] == 0 && uuid->uu[14] == 0) + i = 14; + } + + while (i-- > limit) { + if (i == 11 || i == 9 || i == 7 || i == 5) { + buf[j * 2 + shift] = '-'; + shift++; + } + + sprintf(buf + j * 2 + shift, "%02x", uuid->uu[i]); + ++j; + } + + return buf; +} + +/* + * Tries to convert hex string of given size into out buffer. + * Output buffer is little endian. + */ +static void scan_field(const char *str, int len, uint8_t *out, int out_size) +{ + int i; + + memset(out, 0, out_size); + if (out_size * 2 > len + 1) + out_size = (len + 1) / 2; + + for (i = 0; i < out_size && len > 0; ++i) { + len -= 2; + if (len >= 0) + sscanf(str + len, "%02hhx", &out[i]); + else + sscanf(str, "%1hhx", &out[i]); + } +} + +/* Like strchr but with upper limit instead of 0 terminated string */ +static const char *strchrlimit(const char *p, const char *e, int c) +{ + while (p < e && *p != (char) c) + ++p; + + return p < e ? p : NULL; +} + +/* + * converts string to uuid + * it accepts uuid in following forms: + * 123 + * 0000123 + * 0000123-0014-1234-0000-000056789abc + * 0000123001412340000000056789abc + * 123-14-1234-0-56789abc + */ +static void gatt_str2bt_uuid_t(const char *str, int len, bt_uuid_t *uuid) +{ + int dash_cnt = 0; + int dashes[6] = {-1}; /* indexes of '-' or \0 */ + static uint8_t filed_offset[] = { 16, 12, 10, 8, 6, 0 }; + const char *p = str; + const char *e; + int i; + + e = str + ((len >= 0) ? len : (int) strlen(str)); + + while (p != NULL && dash_cnt < 5) { + const char *f = strchrlimit(p, e, '-'); + + if (f != NULL) + dashes[++dash_cnt] = f++ - str; + p = f; + } + + /* get index of \0 to dashes table */ + if (dash_cnt < 5) + dashes[++dash_cnt] = e - str; + + memcpy(uuid, GATT_BASE_UUID, sizeof(bt_uuid_t)); + + /* whole uuid in one string without dashes */ + if (dash_cnt == 1 && dashes[1] > 8) { + if (dashes[1] > 32) + dashes[1] = 32; + scan_field(str, dashes[1], + &uuid->uu[16 - (dashes[1] + 1) / 2], + (dashes[1] + 1) / 2); + } else { + for (i = 0; i < dash_cnt; ++i) { + scan_field(str + dashes[i] + 1, + dashes[i + 1] - dashes[i] - 1, + &uuid->uu[filed_offset[i + 1]], + filed_offset[i] - filed_offset[i + 1]); + } + } +} + +/* char_id formating function */ +static char *btgatt_gatt_id_t2str(const btgatt_gatt_id_t *char_id, char *buf) +{ + char uuid_buf[MAX_UUID_STR_LEN]; + + sprintf(buf, "{%s,%d}", gatt_uuid_t2str(&char_id->uuid, uuid_buf), + char_id->inst_id); + return buf; +} + +/* Parse btgatt_gatt_id_t */ +static void str2btgatt_gatt_id_t(const char *buf, btgatt_gatt_id_t *char_id) +{ + const char *e; + + memcpy(&char_id->uuid, &GATT_BASE_UUID, sizeof(bt_uuid_t)); + char_id->inst_id = 0; + + if (*buf == '{') + buf++; + e = strpbrk(buf, " ,}"); + if (e == NULL) + e = buf + strlen(buf); + + gatt_str2bt_uuid_t(buf, e - buf, &char_id->uuid); + if (*e == ',') { + buf = e + 1; + e = strpbrk(buf, " ,}"); + if (e == NULL) + e = buf + strlen(buf); + if (buf < e) + char_id->inst_id = atoi(buf); + } +} + +/* service_id formating function */ +static char *btgatt_srvc_id_t2str(const btgatt_srvc_id_t *srvc_id, char *buf) +{ + char uuid_buf[MAX_UUID_STR_LEN]; + + sprintf(buf, "{%s,%d,%d}", gatt_uuid_t2str(&srvc_id->id.uuid, uuid_buf), + srvc_id->id.inst_id, srvc_id->is_primary); + return buf; +} + +/* Parse btgatt_srvc_id_t */ +static void str2btgatt_srvc_id_t(const char *buf, btgatt_srvc_id_t *srvc_id) +{ + const char *e; + + memcpy(&srvc_id->id.uuid, &GATT_BASE_UUID, sizeof(bt_uuid_t)); + srvc_id->id.inst_id = 0; + srvc_id->is_primary = 1; + + if (*buf == '{') + buf++; + e = strpbrk(buf, " ,}"); + if (e == NULL) + e = buf + strlen(buf); + + gatt_str2bt_uuid_t(buf, e - buf, &srvc_id->id.uuid); + if (*e == ',') { + buf = e + 1; + e = strpbrk(buf, " ,}"); + if (e == NULL) + e = buf + strlen(buf); + if (buf < e) + srvc_id->id.inst_id = atoi(buf); + } + + if (*e == ',') { + buf = e + 1; + e = strpbrk(buf, " ,}"); + if (e == NULL) + e = buf + strlen(buf); + if (buf < e) + srvc_id->is_primary = atoi(buf); + } +} + +/* Converts array of uint8_t to string representation */ +static char *array2str(const uint8_t *v, int size, char *buf, int out_size) +{ + int limit = size; + int i; + + if (out_size > 0) { + *buf = '\0'; + if (size >= 2 * out_size) + limit = (out_size - 2) / 2; + + for (i = 0; i < limit; ++i) + sprintf(buf + 2 * i, "%02x", v[i]); + + /* output buffer not enough to hold whole field fill with ...*/ + if (limit < size) + sprintf(buf + 2 * i, "..."); + } + + return buf; +} + +/* Converts btgatt_notify_params_t to string */ +static char *btgatt_notify_params_t2str(const btgatt_notify_params_t *data, + char *buf) +{ + char addr[MAX_ADDR_STR_LEN]; + char srvc_id[MAX_SRVC_ID_STR_LEN]; + char char_id[MAX_CHAR_ID_STR_LEN]; + char value[MAX_HEX_VAL_STR_LEN]; + + sprintf(buf, "{bda=%s, srvc_id=%s, char_id=%s, val=%s, is_notify=%u}", + bt_bdaddr_t2str(&data->bda, addr), + btgatt_srvc_id_t2str(&data->srvc_id, srvc_id), + btgatt_gatt_id_t2str(&data->char_id, char_id), + array2str(data->value, data->len, value, sizeof(value)), + data->is_notify); + return buf; +} + +static char *btgatt_unformatted_value_t2str(const btgatt_unformatted_value_t *v, + char *buf, int size) +{ + return array2str(v->value, v->len, buf, size); +} + +static char *btgatt_read_params_t2str(const btgatt_read_params_t *data, + char *buf) +{ + char srvc_id[MAX_SRVC_ID_STR_LEN]; + char char_id[MAX_CHAR_ID_STR_LEN]; + char descr_id[MAX_UUID_STR_LEN]; + char value[MAX_HEX_VAL_STR_LEN]; + + sprintf(buf, "{srvc_id=%s, char_id=%s, descr_id=%s, val=%s value_type=%d, status=%d}", + btgatt_srvc_id_t2str(&data->srvc_id, srvc_id), + btgatt_gatt_id_t2str(&data->char_id, char_id), + btgatt_descr_id_t2str(&data->descr_id, descr_id), + btgatt_unformatted_value_t2str(&data->value, value, 100), + data->value_type, data->status); + return buf; +} + +/* BT-GATT Client callbacks. */ + +/* Cache client_if and conn_id for tab completion */ +static char client_if_str[20]; +static char conn_id_str[20]; +/* Cache address for tab completion */ +static char last_addr[MAX_ADDR_STR_LEN]; + +/* Callback invoked in response to register_client */ +static void gattc_register_client_cb(int status, int client_if, + bt_uuid_t *app_uuid) +{ + char buf[MAX_UUID_STR_LEN]; + + snprintf(client_if_str, sizeof(client_if_str), "%d", client_if); + + haltest_info("%s: status=%d client_if=%d app_uuid=%s\n", __func__, + status, client_if, + gatt_uuid_t2str(app_uuid, buf)); +} + +/* Callback for scan results */ +static void gattc_scan_result_cb(bt_bdaddr_t *bda, int rssi, uint8_t *adv_data) +{ + char buf[MAX_ADDR_STR_LEN]; + + haltest_info("%s: bda=%s rssi=%d adv_data=%p\n", __func__, + bt_bdaddr_t2str(bda, buf), rssi, adv_data); +} + +/* GATT open callback invoked in response to open */ +static void gattc_connect_cb(int conn_id, int status, int client_if, + bt_bdaddr_t *bda) +{ + haltest_info("%s: conn_id=%d status=%d, client_if=%d bda=%s\n", + __func__, conn_id, status, client_if, + bt_bdaddr_t2str(bda, last_addr)); +} + +/* Callback invoked in response to close */ +static void gattc_disconnect_cb(int conn_id, int status, int client_if, + bt_bdaddr_t *bda) +{ + char buf[MAX_ADDR_STR_LEN]; + + haltest_info("%s: conn_id=%d status=%d, client_if=%d bda=%s\n", + __func__, conn_id, status, client_if, + bt_bdaddr_t2str(bda, buf)); +} + +/* + * Invoked in response to search_service when the GATT service search + * has been completed. + */ +static void gattc_search_complete_cb(int conn_id, int status) +{ + haltest_info("%s: conn_id=%d status=%d\n", __func__, conn_id, status); +} + +/* Reports GATT services on a remote device */ +static void gattc_search_result_cb(int conn_id, btgatt_srvc_id_t *srvc_id) +{ + char srvc_id_buf[MAX_SRVC_ID_STR_LEN]; + + haltest_info("%s: conn_id=%d srvc_id=%s\n", __func__, conn_id, + btgatt_srvc_id_t2str(srvc_id, srvc_id_buf)); +} + +/* GATT characteristic enumeration result callback */ +static void gattc_get_characteristic_cb(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id, + int char_prop) +{ + char srvc_id_buf[MAX_SRVC_ID_STR_LEN]; + char char_id_buf[MAX_CHAR_ID_STR_LEN]; + + haltest_info("%s: conn_id=%d status=%d srvc_id=%s char_id=%s, char_prop=0x%x\n", + __func__, conn_id, status, + btgatt_srvc_id_t2str(srvc_id, srvc_id_buf), + btgatt_gatt_id_t2str(char_id, char_id_buf), char_prop); + + /* enumerate next characteristic */ + if (status == 0) + EXEC(if_gatt->client->get_characteristic, conn_id, srvc_id, + char_id); +} + +/* GATT descriptor enumeration result callback */ +static void gattc_get_descriptor_cb(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + btgatt_descr_id_t *descr_id) +{ + char buf[MAX_UUID_STR_LEN]; + char srvc_id_buf[MAX_SRVC_ID_STR_LEN]; + char char_id_buf[MAX_CHAR_ID_STR_LEN]; + + haltest_info("%s: conn_id=%d status=%d srvc_id=%s char_id=%s, descr_id=%s\n", + __func__, conn_id, status, + btgatt_srvc_id_t2str(srvc_id, srvc_id_buf), + btgatt_gatt_id_t2str(char_id, char_id_buf), + btgatt_descr_id_t2str(descr_id, buf)); + + if (status == 0) + EXEC(if_gatt->client->get_descriptor, conn_id, srvc_id, char_id, + descr_id); +} + +/* GATT included service enumeration result callback */ +static void gattc_get_included_service_cb(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, + btgatt_srvc_id_t *incl_srvc_id) +{ + char srvc_id_buf[MAX_SRVC_ID_STR_LEN]; + char incl_srvc_id_buf[MAX_SRVC_ID_STR_LEN]; + + haltest_info("%s: conn_id=%d status=%d srvc_id=%s incl_srvc_id=%s)\n", + __func__, conn_id, status, + btgatt_srvc_id_t2str(srvc_id, srvc_id_buf), + btgatt_srvc_id_t2str(incl_srvc_id, incl_srvc_id_buf)); + + if (status == 0) + EXEC(if_gatt->client->get_included_service, conn_id, srvc_id, + incl_srvc_id); +} + +/* Callback invoked in response to [de]register_for_notification */ +static void gattc_register_for_notification_cb(int conn_id, int registered, + int status, + btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id) +{ + char srvc_id_buf[MAX_SRVC_ID_STR_LEN]; + char char_id_buf[MAX_CHAR_ID_STR_LEN]; + + haltest_info("%s: conn_id=%d registered=%d status=%d srvc_id=%s char_id=%s\n", + __func__, conn_id, registered, status, + btgatt_srvc_id_t2str(srvc_id, srvc_id_buf), + btgatt_gatt_id_t2str(char_id, char_id_buf)); +} + +/* + * Remote device notification callback, invoked when a remote device sends + * a notification or indication that a client has registered for. + */ +static void gattc_notify_cb(int conn_id, btgatt_notify_params_t *p_data) +{ + char buf[MAX_NOTIFY_PARAMS_STR_LEN]; + + haltest_info("%s: conn_id=%d data=%s\n", __func__, conn_id, + btgatt_notify_params_t2str(p_data, buf)); +} + +/* Reports result of a GATT read operation */ +static void gattc_read_characteristic_cb(int conn_id, int status, + btgatt_read_params_t *p_data) +{ + char buf[MAX_READ_PARAMS_STR_LEN]; + + haltest_info("%s: conn_id=%d status=%d data=%s\n", __func__, conn_id, + status, btgatt_read_params_t2str(p_data, buf)); +} + +/* GATT write characteristic operation callback */ +static void gattc_write_characteristic_cb(int conn_id, int status, + btgatt_write_params_t *p_data) +{ + haltest_info("%s: conn_id=%d status=%d\n", __func__, conn_id, status); +} + +/* GATT execute prepared write callback */ +static void gattc_execute_write_cb(int conn_id, int status) +{ + haltest_info("%s: conn_id=%d status=%d\n", __func__, conn_id, status); +} + +/* Callback invoked in response to read_descriptor */ +static void gattc_read_descriptor_cb(int conn_id, int status, + btgatt_read_params_t *p_data) +{ + char buf[MAX_READ_PARAMS_STR_LEN]; + + haltest_info("%s: conn_id=%d status=%d data=%s\n", __func__, conn_id, + status, btgatt_read_params_t2str(p_data, buf)); +} + +/* Callback invoked in response to write_descriptor */ +static void gattc_write_descriptor_cb(int conn_id, int status, + btgatt_write_params_t *p_data) +{ + haltest_info("%s: conn_id=%d status=%d\n", __func__, conn_id, status); +} + +/* Callback triggered in response to read_remote_rssi */ +static void gattc_read_remote_rssi_cb(int client_if, bt_bdaddr_t *bda, int rssi, + int status) +{ + char buf[MAX_ADDR_STR_LEN]; + + haltest_info("%s: client_if=%d bda=%s rssi=%d satus=%d\n", __func__, + client_if, bt_bdaddr_t2str(bda, buf), rssi, status); +} + +/* Callback invoked in response to listen */ +static void gattc_listen_cb(int status, int client_if) +{ + haltest_info("%s: client_if=%d status=%d\n", __func__, client_if, + status); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +/* Callback invoked when the MTU for a given connection changes */ +static void gattc_configure_mtu_cb(int conn_id, int status, int mtu) +{ + haltest_info("%s: conn_id=%d, status=%d, mtu=%d\n", __func__, conn_id, + status, mtu); +} + +/* Callback invoked when a scan filter configuration command has completed */ +static void gattc_scan_filter_cfg_cb(int action, int client_if, int status, + int filt_type, int avbl_space) +{ + haltest_info("%s: action=%d, client_if=%d, status=%d, filt_type=%d" + ", avbl_space=%d", __func__, action, client_if, status, + filt_type, avbl_space); +} + +/* Callback invoked when scan param has been added, cleared, or deleted */ +static void gattc_scan_filter_param_cb(int action, int client_if, int status, + int avbl_space) +{ + haltest_info("%s: action=%d, client_if=%d, status=%d, avbl_space=%d", + __func__, action, client_if, status, avbl_space); +} + +/* Callback invoked when a scan filter configuration command has completed */ +static void gattc_scan_filter_status_cb(int enable, int client_if, int status) +{ + haltest_info("%s: enable=%d, client_if=%d, status=%d", __func__, + enable, client_if, status); +} + +/* Callback invoked when multi-adv enable operation has completed */ +static void gattc_multi_adv_enable_cb(int client_if, int status) +{ + haltest_info("%s: client_if=%d, status=%d", __func__, client_if, + status); +} + +/* Callback invoked when multi-adv param update operation has completed */ +static void gattc_multi_adv_update_cb(int client_if, int status) +{ + haltest_info("%s: client_if=%d, status=%d", __func__, client_if, + status); +} + +/* Callback invoked when multi-adv instance data set operation has completed */ +static void gattc_multi_adv_data_cb(int client_if, int status) +{ + haltest_info("%s: client_if=%d, status=%d", __func__, client_if, + status); +} + +/* Callback invoked when multi-adv disable operation has completed */ +static void gattc_multi_adv_disable_cb(int client_if, int status) +{ + haltest_info("%s: client_if=%d, status=%d", __func__, client_if, + status); +} + +/* + * Callback notifying an application that a remote device connection is + * currently congested and cannot receive any more data. An application should + * avoid sending more data until a further callback is received indicating the + * congestion status has been cleared. + */ +static void gattc_congestion_cb(int conn_id, bool congested) +{ + haltest_info("%s: conn_id=%d, congested=%d", __func__, conn_id, + congested); +} + +/* Callback invoked when batchscan storage config operation has completed */ +static void gattc_batchscan_cfg_storage_cb(int client_if, int status) +{ + haltest_info("%s: client_if=%d, status=%d", __func__, client_if, + status); +} + +/* Callback invoked when batchscan enable / disable operation has completed */ +static void gattc_batchscan_enable_disable_cb(int action, int client_if, + int status) +{ + haltest_info("%s: action=%d, client_if=%d, status=%d", __func__, action, + client_if, status); +} + +/* Callback invoked when batchscan reports are obtained */ +static void gattc_batchscan_reports_cb(int client_if, int status, + int report_format, int num_records, + int data_len, uint8_t* rep_data) +{ + /* BTGATT_MAX_ATTR_LEN = 600 */ + char valbuf[600]; + + haltest_info("%s: client_if=%d, status=%d, report_format=%d" + ", num_records=%d, data_len=%d, rep_data=%s", __func__, + client_if, status, report_format, num_records, data_len, + array2str(rep_data, data_len, valbuf, sizeof(valbuf))); +} + +/* Callback invoked when batchscan storage threshold limit is crossed */ +static void gattc_batchscan_threshold_cb(int client_if) +{ + haltest_info("%s: client_if=%d", __func__, client_if); +} + +/* Track ADV VSE callback invoked when tracked device is found or lost */ +static void gattc_track_adv_event_cb(int client_if, int filt_index, + int addr_type, bt_bdaddr_t* bda, + int adv_state) +{ + char buf[MAX_ADDR_STR_LEN]; + + haltest_info("%s, client_if=%d, filt_index=%d, addr_type=%d, bda=%s" + ", adv_state=%d", __func__, client_if, filt_index, + addr_type, bt_bdaddr_t2str(bda, buf), adv_state); +} +#endif + +static const btgatt_client_callbacks_t btgatt_client_callbacks = { + .register_client_cb = gattc_register_client_cb, + .scan_result_cb = gattc_scan_result_cb, + .open_cb = gattc_connect_cb, + .close_cb = gattc_disconnect_cb, + .search_complete_cb = gattc_search_complete_cb, + .search_result_cb = gattc_search_result_cb, + .get_characteristic_cb = gattc_get_characteristic_cb, + .get_descriptor_cb = gattc_get_descriptor_cb, + .get_included_service_cb = gattc_get_included_service_cb, + .register_for_notification_cb = gattc_register_for_notification_cb, + .notify_cb = gattc_notify_cb, + .read_characteristic_cb = gattc_read_characteristic_cb, + .write_characteristic_cb = gattc_write_characteristic_cb, + .read_descriptor_cb = gattc_read_descriptor_cb, + .write_descriptor_cb = gattc_write_descriptor_cb, + .execute_write_cb = gattc_execute_write_cb, + .read_remote_rssi_cb = gattc_read_remote_rssi_cb, + .listen_cb = gattc_listen_cb, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .configure_mtu_cb = gattc_configure_mtu_cb, + .scan_filter_cfg_cb = gattc_scan_filter_cfg_cb, + .scan_filter_param_cb = gattc_scan_filter_param_cb, + .scan_filter_status_cb = gattc_scan_filter_status_cb, + .multi_adv_enable_cb = gattc_multi_adv_enable_cb, + .multi_adv_update_cb = gattc_multi_adv_update_cb, + .multi_adv_data_cb = gattc_multi_adv_data_cb, + .multi_adv_disable_cb = gattc_multi_adv_disable_cb, + .congestion_cb = gattc_congestion_cb, + .batchscan_cfg_storage_cb = gattc_batchscan_cfg_storage_cb, + .batchscan_enb_disable_cb = gattc_batchscan_enable_disable_cb, + .batchscan_reports_cb = gattc_batchscan_reports_cb, + .batchscan_threshold_cb = gattc_batchscan_threshold_cb, + .track_adv_event_cb = gattc_track_adv_event_cb, +#endif +}; + +/* BT-GATT Server callbacks */ + +/* Cache server_if and conn_id for tab completion */ +static char server_if_str[20]; + +/* Callback invoked in response to register_server */ +static void gatts_register_server_cb(int status, int server_if, + bt_uuid_t *app_uuid) +{ + char buf[MAX_UUID_STR_LEN]; + + haltest_info("%s: status=%d server_if=%d app_uuid=%s\n", __func__, + status, server_if, gatt_uuid_t2str(app_uuid, buf)); +} + +/* + * Callback indicating that a remote device has connected + * or been disconnected + */ +static void gatts_connection_cb(int conn_id, int server_if, int connected, + bt_bdaddr_t *bda) +{ + haltest_info("%s: conn_id=%d server_if=%d connected=%d bda=%s\n", + __func__, conn_id, server_if, connected, + bt_bdaddr_t2str(bda, last_addr)); + snprintf(conn_id_str, sizeof(conn_id_str), "%d", conn_id); +} + +/* Callback invoked in response to create_service */ +static void gatts_service_added_cb(int status, int server_if, + btgatt_srvc_id_t *srvc_id, int srvc_handle) +{ + char buf[MAX_SRVC_ID_STR_LEN]; + + snprintf(server_if_str, sizeof(server_if_str), "%d", server_if); + + haltest_info("%s: status=%d server_if=%d srvc_id=%s handle=0x%x\n", + __func__, status, server_if, + btgatt_srvc_id_t2str(srvc_id, buf), srvc_handle); +} + +/* Callback indicating that an included service has been added to a service */ +static void gatts_included_service_added_cb(int status, int server_if, + int srvc_handle, + int incl_srvc_handle) +{ + haltest_info("%s: status=%d server_if=%d srvc_handle=0x%x inc_srvc_handle=0x%x\n", + __func__, status, server_if, + srvc_handle, incl_srvc_handle); +} + +/* Callback invoked when a characteristic has been added to a service */ +static void gatts_characteristic_added_cb(int status, int server_if, + bt_uuid_t *uuid, + int srvc_handle, + int char_handle) +{ + char buf[MAX_SRVC_ID_STR_LEN]; + + haltest_info("%s: status=%d server_if=%d uuid=%s srvc_handle=0x%x char_handle=0x%x\n", + __func__, status, server_if, gatt_uuid_t2str(uuid, buf), + srvc_handle, char_handle); +} + +/* Callback invoked when a descriptor has been added to a characteristic */ +static void gatts_descriptor_added_cb(int status, int server_if, + bt_uuid_t *uuid, int srvc_handle, + int descr_handle) +{ + char buf[MAX_SRVC_ID_STR_LEN]; + + haltest_info("%s: status=%d server_if=%d uuid=%s srvc_handle=0x%x descr_handle=0x%x\n", + __func__, status, server_if, gatt_uuid_t2str(uuid, buf), + srvc_handle, descr_handle); +} + +/* Callback invoked in response to start_service */ +static void gatts_service_started_cb(int status, int server_if, int srvc_handle) +{ + haltest_info("%s: status=%d server_if=%d srvc_handle=0x%x\n", + __func__, status, server_if, srvc_handle); +} + +/* Callback invoked in response to stop_service */ +static void gatts_service_stopped_cb(int status, int server_if, int srvc_handle) +{ + haltest_info("%s: status=%d server_if=%d srvc_handle=0x%x\n", + __func__, status, server_if, srvc_handle); +} + +/* Callback triggered when a service has been deleted */ +static void gatts_service_deleted_cb(int status, int server_if, int srvc_handle) +{ + haltest_info("%s: status=%d server_if=%d srvc_handle=0x%x\n", + __func__, status, server_if, srvc_handle); +} + +/* + * Callback invoked when a remote device has requested to read a characteristic + * or descriptor. The application must respond by calling send_response + */ +static void gatts_request_read_cb(int conn_id, int trans_id, bt_bdaddr_t *bda, + int attr_handle, int offset, + bool is_long) +{ + char buf[MAX_ADDR_STR_LEN]; + + haltest_info("%s: conn_id=%d trans_id=%d bda=%s attr_handle=0x%x offset=%d is_long=%d\n", + __func__, conn_id, trans_id, bt_bdaddr_t2str(bda, buf), + attr_handle, offset, is_long); +} + +/* + * Callback invoked when a remote device has requested to write to a + * characteristic or descriptor. + */ +static void gatts_request_write_cb(int conn_id, int trans_id, bt_bdaddr_t *bda, + int attr_handle, int offset, int length, + bool need_rsp, bool is_prep, + uint8_t *value) +{ + char buf[MAX_ADDR_STR_LEN]; + char valbuf[100]; + + haltest_info("%s: conn_id=%d trans_id=%d bda=%s attr_handle=0x%x offset=%d length=%d need_rsp=%d is_prep=%d value=%s\n", + __func__, conn_id, trans_id, bt_bdaddr_t2str(bda, buf), + attr_handle, offset, length, need_rsp, is_prep, + array2str(value, length, valbuf, sizeof(valbuf))); +} + +/* Callback invoked when a previously prepared write is to be executed */ +static void gatts_request_exec_write_cb(int conn_id, int trans_id, + bt_bdaddr_t *bda, int exec_write) +{ + char buf[MAX_ADDR_STR_LEN]; + + haltest_info("%s: conn_id=%d trans_id=%d bda=%s exec_write=%d\n", + __func__, conn_id, trans_id, bt_bdaddr_t2str(bda, buf), + exec_write); +} + +/* + * Callback triggered in response to send_response if the remote device + * sends a confirmation. + */ +static void gatts_response_confirmation_cb(int status, int handle) +{ + haltest_info("%s: status=%d handle=0x%x\n", __func__, status, handle); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +/** + * Callback confirming that a notification or indication has been sent + * to a remote device. + */ +static void gatts_indication_sent_cb(int conn_id, int status) +{ + haltest_info("%s: status=%d conn_id=%d", __func__, status, conn_id); +} + +/** + * Callback notifying an application that a remote device connection is + * currently congested and cannot receive any more data. An application + * should avoid sending more data until a further callback is received + * indicating the congestion status has been cleared. + */ +static void gatts_congestion_cb(int conn_id, bool congested) +{ + haltest_info("%s: conn_id=%d congested=%d", __func__, conn_id, + congested); +} +#endif + +static const btgatt_server_callbacks_t btgatt_server_callbacks = { + .register_server_cb = gatts_register_server_cb, + .connection_cb = gatts_connection_cb, + .service_added_cb = gatts_service_added_cb, + .included_service_added_cb = gatts_included_service_added_cb, + .characteristic_added_cb = gatts_characteristic_added_cb, + .descriptor_added_cb = gatts_descriptor_added_cb, + .service_started_cb = gatts_service_started_cb, + .service_stopped_cb = gatts_service_stopped_cb, + .service_deleted_cb = gatts_service_deleted_cb, + .request_read_cb = gatts_request_read_cb, + .request_write_cb = gatts_request_write_cb, + .request_exec_write_cb = gatts_request_exec_write_cb, + .response_confirmation_cb = gatts_response_confirmation_cb, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .indication_sent_cb = gatts_indication_sent_cb, + .congestion_cb = gatts_congestion_cb, +#endif +}; + +static const btgatt_callbacks_t gatt_cbacks = { + .size = sizeof(gatt_cbacks), + .client = &btgatt_client_callbacks, + .server = &btgatt_server_callbacks +}; + +/* + * convert hex string to uint8_t array + */ +static int fill_buffer(const char *str, uint8_t *out, int out_size) +{ + int str_len; + int i, j; + char c; + uint8_t b; + + str_len = strlen(str); + + /* Hex string must be byte format */ + if (str_len % 2) + return -1; + + for (i = 0, j = 0; i < out_size && j < str_len; i++, j++) { + c = str[j]; + + if (c >= 'a' && c <= 'f') + c += 'A' - 'a'; + + if (c >= '0' && c <= '9') + b = c - '0'; + else if (c >= 'A' && c <= 'F') + b = 10 + c - 'A'; + else + return 0; + + j++; + + c = str[j]; + + if (c >= 'a' && c <= 'f') + c += 'A' - 'a'; + + if (c >= '0' && c <= '9') + b = b * 16 + c - '0'; + else if (c >= 'A' && c <= 'F') + b = b * 16 + 10 + c - 'A'; + else + return 0; + + out[i] = b; + } + + return i; +} + +/* gatt client methods */ + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_gatt); + + EXEC(if_gatt->init, &gatt_cbacks); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_gatt); + + EXECV(if_gatt->cleanup); + + if_gatt = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface gatt_if = { + .name = "gatt", + .methods = methods +}; + +/* register_client */ + +static void register_client_p(int argc, const char **argv) +{ + bt_uuid_t uuid; + + RETURN_IF_NULL(if_gatt); + + /* uuid */ + if (argc <= 2) + gatt_str2bt_uuid_t("babe4bed", -1, &uuid); + else + gatt_str2bt_uuid_t(argv[2], -1, &uuid); + + EXEC(if_gatt->client->register_client, &uuid); +} + +/* unregister_client */ + +static void unregister_client_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void unregister_client_p(int argc, const char **argv) +{ + int client_if; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + + EXEC(if_gatt->client->unregister_client, client_if); +} + +/* scan */ + +/* Same completion as unregister for now, start stop is not auto completed */ +#define scan_c unregister_client_c + +static void scan_p(int argc, const char **argv) +{ +#if ANDROID_VERSION < PLATFORM_VER(5, 0, 0) + int client_if; +#endif + int start = 1; + + RETURN_IF_NULL(if_gatt); + +#if ANDROID_VERSION < PLATFORM_VER(5, 0, 0) + VERIFY_CLIENT_IF(2, client_if); + + /* start */ + if (argc >= 4) + start = strtol(argv[3], NULL, 0); + + EXEC(if_gatt->client->scan, client_if, start); +#else + /* start */ + if (argc >= 3) + start = strtol(argv[2], NULL, 0); + + EXEC(if_gatt->client->scan, start); +#endif +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = client_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = NULL; + *enum_func = enum_devices; + } +} + +static void connect_p(int argc, const char **argv) +{ + int client_if; + bt_bdaddr_t bd_addr; + int is_direct = 1; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + int transport = 1; +#endif + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ADDR_ARG(3, &bd_addr); + + /* is_direct */ + if (argc > 4) + is_direct = strtol(argv[4], NULL, 0); + +#if ANDROID_VERSION < PLATFORM_VER(5, 0, 0) + EXEC(if_gatt->client->connect, client_if, &bd_addr, is_direct); +#else + /* transport */ + if (argc > 5) + transport = strtol(argv[5], NULL, 0); + + EXEC(if_gatt->client->connect, client_if, &bd_addr, is_direct, + transport); +#endif +} + +/* disconnect */ + +static void disconnect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = client_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = last_addr; + *enum_func = enum_one_string; + } else if (argc == 5) { + *user = conn_id_str; + *enum_func = enum_one_string; + } +} + +static void disconnect_p(int argc, const char **argv) +{ + int client_if; + bt_bdaddr_t bd_addr; + int conn_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ADDR_ARG(3, &bd_addr); + VERIFY_CONN_ID(4, conn_id); + + EXEC(if_gatt->client->disconnect, client_if, &bd_addr, conn_id); +} + +/* listen */ + +/* Same completion as unregister for now, start stop is not auto completed */ +#define listen_c unregister_client_c + +static void listen_p(int argc, const char **argv) +{ + int client_if; + int start = 1; + + RETURN_IF_NULL(if_gatt); + + VERIFY_CLIENT_IF(2, client_if); + + /* start */ + if (argc >= 4) + start = strtol(argv[3], NULL, 0); + + EXEC(if_gatt->client->listen, client_if, start); +} + +/* refresh */ + +static void refresh_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = client_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *enum_func = enum_devices; + } +} + +static void refresh_p(int argc, const char **argv) +{ + int client_if; + bt_bdaddr_t bd_addr; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ADDR_ARG(3, &bd_addr); + + EXEC(if_gatt->client->refresh, client_if, &bd_addr); +} + +/* search_service */ + +static void search_service_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = conn_id_str; + *enum_func = enum_one_string; + } +} + +static void search_service_p(int argc, const char **argv) +{ + int conn_id; + + RETURN_IF_NULL(if_gatt); + + VERIFY_CONN_ID(2, conn_id); + + /* uuid */ + if (argc <= 3) { + EXEC(if_gatt->client->search_service, conn_id, NULL); + + } else { + bt_uuid_t filter_uuid; + + gatt_str2bt_uuid_t(argv[3], -1, &filter_uuid); + EXEC(if_gatt->client->search_service, conn_id, &filter_uuid); + } +} + +/* get_included_service */ + +static void get_included_service_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = conn_id_str; + *enum_func = enum_one_string; + } +} + +static void get_included_service_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + + EXEC(if_gatt->client->get_included_service, conn_id, &srvc_id, NULL); +} + +/* get_characteristic */ + +/* Same completion as get_included_service_c */ +#define get_characteristic_c get_included_service_c + +static void get_characteristic_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + + EXEC(if_gatt->client->get_characteristic, conn_id, &srvc_id, NULL); +} + +/* get_descriptor */ + +/* Same completion as get_included_service_c */ +#define get_descriptor_c get_included_service_c + +static void get_descriptor_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + VERIFY_CHAR_ID(4, &char_id); + + EXEC(if_gatt->client->get_descriptor, conn_id, &srvc_id, &char_id, + NULL); +} + +/* read_characteristic */ + +/* Same completion as get_included_service_c */ +#define read_characteristic_c get_included_service_c + +static void read_characteristic_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + int auth_req = 0; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + VERIFY_CHAR_ID(4, &char_id); + + /* auth_req */ + if (argc > 5) + auth_req = strtol(argv[5], NULL, 0); + + EXEC(if_gatt->client->read_characteristic, conn_id, &srvc_id, &char_id, + auth_req); +} + +/* write_characteristic */ + +static void write_characteristic_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + /* + * This should be from tGATT_WRITE_TYPE but it's burried + * inside bluedroid guts + */ + static const char *wrtypes[] = { "1", "2", "3", NULL }; + + if (argc == 3) { + *user = conn_id_str; + *enum_func = enum_one_string; + } else if (argc == 6) { + *user = wrtypes; + *enum_func = enum_strings; + } +} + +static void write_characteristic_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + int write_type; + int len; + int auth_req = 0; + uint8_t value[100]; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + VERIFY_CHAR_ID(4, &char_id); + + /* write type */ + if (argc <= 5) { + haltest_error("No write type specified\n"); + return; + } + write_type = strtol(argv[5], NULL, 0); + + GET_VERIFY_HEX_STRING(6, value, len); + + /* auth_req */ + if (argc > 7) + auth_req = strtol(argv[7], NULL, 0); + + EXEC(if_gatt->client->write_characteristic, conn_id, &srvc_id, &char_id, + write_type, len, auth_req, (char *) value); +} + +/* read_descriptor */ + +/* Same completion as get_included_service_c */ +#define read_descriptor_c get_included_service_c + +static void read_descriptor_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + btgatt_descr_id_t descr_id; + int auth_req = 0; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + VERIFY_CHAR_ID(4, &char_id); + VERIFY_DESCR_ID(5, &descr_id); + + /* auth_req */ + if (argc > 6) + auth_req = strtol(argv[6], NULL, 0); + + EXEC(if_gatt->client->read_descriptor, conn_id, &srvc_id, &char_id, + &descr_id, auth_req); +} + +/* write_descriptor */ + +static void write_descriptor_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + /* + * This should be from tGATT_WRITE_TYPE but it's burried + * inside bluedroid guts + */ + static const char *wrtypes[] = { "1", "2", "3", NULL }; + + if (argc == 3) { + *user = conn_id_str; + *enum_func = enum_one_string; + } else if (argc == 7) { + *user = wrtypes; + *enum_func = enum_strings; + } +} + +static void write_descriptor_p(int argc, const char **argv) +{ + int conn_id; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + btgatt_descr_id_t descr_id; + int write_type; + int len; + int auth_req = 0; + uint8_t value[200] = {0}; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_SRVC_ID(3, &srvc_id); + VERIFY_CHAR_ID(4, &char_id); + VERIFY_DESCR_ID(5, &descr_id); + + /* write type */ + if (argc <= 6) { + haltest_error("No write type specified\n"); + return; + } + write_type = strtol(argv[6], NULL, 0); + + /* value */ + if (argc <= 7) { + haltest_error("No value specified\n"); + return; + } + + /* len in chars */ + if (strncmp(argv[7], "0X", 2) && strncmp(argv[7], "0x", 2)) { + haltest_error("Value must be hex string"); + return; + } + + len = fill_buffer(argv[7] + 2, value, sizeof(value)); + + /* auth_req */ + if (argc > 8) + auth_req = strtol(argv[8], NULL, 0); + + EXEC(if_gatt->client->write_descriptor, conn_id, &srvc_id, &char_id, + &descr_id, write_type, len, auth_req, (char *) value); +} + +/* execute_write */ + +/* Same completion as search_service */ +#define execute_write_c search_service_c + +static void execute_write_p(int argc, const char **argv) +{ + int conn_id; + int execute; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + + /* execute */ + if (argc <= 3) { + haltest_error("No execute specified\n"); + return; + } + execute = strtol(argv[3], NULL, 0); + + EXEC(if_gatt->client->execute_write, conn_id, execute); +} + +/* register_for_notification */ + +static void register_for_notification_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = client_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +static void register_for_notification_p(int argc, const char **argv) +{ + int client_if; + bt_bdaddr_t bd_addr; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ADDR_ARG(3, &bd_addr); + VERIFY_SRVC_ID(4, &srvc_id); + VERIFY_CHAR_ID(5, &char_id); + + EXEC(if_gatt->client->register_for_notification, client_if, &bd_addr, + &srvc_id, &char_id); +} + +/* deregister_for_notification */ + +/* Same completion as search_service */ +#define deregister_for_notification_c register_for_notification_c + +static void deregister_for_notification_p(int argc, const char **argv) +{ + int client_if; + bt_bdaddr_t bd_addr; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ADDR_ARG(3, &bd_addr); + VERIFY_SRVC_ID(4, &srvc_id); + VERIFY_CHAR_ID(5, &char_id); + + EXEC(if_gatt->client->deregister_for_notification, client_if, &bd_addr, + &srvc_id, &char_id); +} + +/* read_remote_rssi */ + +static void read_remote_rssi_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = client_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *enum_func = enum_devices; + } +} + +static void read_remote_rssi_p(int argc, const char **argv) +{ + int client_if; + bt_bdaddr_t bd_addr; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ADDR_ARG(3, &bd_addr); + + EXEC(if_gatt->client->read_remote_rssi, client_if, &bd_addr); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +/* scan filter parameter setup */ +static void scan_filter_param_setup_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void scan_filter_param_setup_p(int argc, const char **argv) +{ + int client_if; + int action; + int filt_index; + int feat_seln; + int list_logic_type, filt_logic_type; + int rssi_high_thres, rssi_low_thres; + int dely_mode; + int found_timeout, lost_timeout, found_timeout_cnt; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ACTION(3, action); + VERIFY_FILT_INDEX(4, filt_index); + VERIFY_FEAT_SELN(5, feat_seln); + VERIFY_LIST_LOGIC_TYPE(6, list_logic_type); + VERIFY_FILT_LOGIC_TYPE(7, filt_logic_type); + VERIFY_RSSI_HI_THR(8, rssi_high_thres); + VERIFY_RSSI_LOW_THR(9, rssi_low_thres); + VERIFY_DELY_MODE(10, dely_mode); + VERIFY_FND_TIME(11, found_timeout); + VERIFY_LOST_TIME(12, lost_timeout); + VERIFY_FND_TIME_CNT(13, found_timeout_cnt); + + EXEC(if_gatt->client->scan_filter_param_setup, client_if, action, + filt_index, feat_seln, list_logic_type, filt_logic_type, + rssi_high_thres, rssi_low_thres, dely_mode, found_timeout, + lost_timeout, found_timeout_cnt); +} + +/* scan filter add remove */ + +static void scan_filter_add_remove_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } else if (argc == 10) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +static void scan_filter_add_remove_p(int argc, const char **argv) +{ + int client_if; + int action; + int filt_type; + int filt_index; + int company_id; + int company_id_mask; + bt_uuid_t p_uuid; + bt_uuid_t p_uuid_mask; + bt_bdaddr_t bd_addr; + char addr_type; + int data_len; + uint8_t p_data[100]; + int mask_len; + uint8_t p_mask[100]; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_ACTION(3, action); + VERIFY_FILT_TYPE(4, filt_type); + VERIFY_FILT_INDEX(5, filt_index); + VERIFY_COMP_ID(6, company_id); + VERIFY_COMP_ID_MASK(7, company_id_mask); + + if (argc <= 9) { + haltest_error("No p_uuid, p_uuid_mask specified\n"); + return; + } + gatt_str2bt_uuid_t(argv[8], -1, &p_uuid); + gatt_str2bt_uuid_t(argv[9], -1, &p_uuid_mask); + + VERIFY_UUID(8, &p_uuid); + VERIFY_UUID(9, &p_uuid_mask); + VERIFY_ADDR_ARG(10, &bd_addr); + VERIFY_ADDR_TYPE(11, addr_type); + GET_VERIFY_HEX_STRING(12, p_data, data_len); + GET_VERIFY_HEX_STRING(13, p_mask, mask_len); + + EXEC(if_gatt->client->scan_filter_add_remove, client_if, action, + filt_type, filt_index, company_id, company_id_mask, + &p_uuid, &p_uuid_mask, &bd_addr, addr_type, data_len, + (char *) p_data, mask_len, (char *) p_mask); +} + +/* scan filter clean */ +static void scan_filter_clear_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void scan_filter_clear_p(int argc, const char **argv) +{ + int client_if; + int filt_index; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_FILT_INDEX(3, filt_index); + + EXEC(if_gatt->client->scan_filter_clear, client_if, filt_index); +} + +/* scan filter enable */ +static void scan_filter_enable_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void scan_filter_enable_p(int argc, const char **argv) +{ + int client_if; + int enable = 0; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + + /* enable */ + if (argc >= 4) + enable = strtol(argv[3], NULL, 0); + + EXEC(if_gatt->client->scan_filter_clear, client_if, enable); +} + +/* set advertising data */ +static void set_adv_data_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void set_adv_data_p(int argc, const char **argv) +{ + int client_if; + bool set_scan_rsp; + bool include_name, include_txpower; + int min_interval, max_interval; + int appearance; + uint16_t manufacturer_len; + uint8_t manufacturer_data[100]; + uint16_t service_data_len; + uint8_t service_data[100]; + uint16_t service_uuid_len; + uint8_t service_uuid[100]; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + + /* set scan response */ + if (argc >= 4) + set_scan_rsp = strtol(argv[3], NULL, 0); + /* include name */ + if (argc >= 5) + include_name = strtol(argv[4], NULL, 0); + /* include txpower */ + if (argc >= 6) + include_txpower = strtol(argv[5], NULL, 0); + + VERIFY_MIN_INTERVAL(6, min_interval); + VERIFY_MAX_INTERVAL(7, max_interval); + VERIFY_APPEARANCE(8, appearance); + GET_VERIFY_HEX_STRING(9, manufacturer_data, manufacturer_len); + GET_VERIFY_HEX_STRING(10, service_data, service_data_len); + GET_VERIFY_HEX_STRING(11, service_uuid, service_uuid_len); + + EXEC(if_gatt->client->set_adv_data, client_if, set_scan_rsp, + include_name, include_txpower, min_interval, max_interval, + appearance, manufacturer_len, (char *) manufacturer_data, + service_data_len, (char *) service_data, service_uuid_len, + (char *) service_uuid); +} + +/* configure mtu */ +static void configure_mtu_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = conn_id_str; + *enum_func = enum_one_string; + } +} + +static void configure_mtu_p(int argc, const char **argv) +{ + int conn_id; + int mtu; + + RETURN_IF_NULL(if_gatt); + VERIFY_CONN_ID(2, conn_id); + VERIFY_MTU(3, mtu); + + EXEC(if_gatt->client->configure_mtu, conn_id, mtu); +} + +/* con parameter update */ +static void conn_parameter_update_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +static void conn_parameter_update_p(int argc, const char **argv) +{ + bt_bdaddr_t bd_addr; + int min_interval, max_interval; + int latency; + int timeout; + + RETURN_IF_NULL(if_gatt); + VERIFY_ADDR_ARG(2, &bd_addr); + VERIFY_MIN_INTERVAL(3, min_interval); + VERIFY_MAX_INTERVAL(4, max_interval); + VERIFY_LATENCY(5, latency); + VERIFY_TIMEOUT(6, timeout); + + EXEC(if_gatt->client->conn_parameter_update, &bd_addr, min_interval, + max_interval, latency, timeout); +} + +/* set scan parameters */ +static void set_scan_parameters_p(int argc, const char **argv) +{ + int scan_interval; + int scan_window; + + RETURN_IF_NULL(if_gatt); + VERIFY_SCAN_INTERVAL(2, scan_interval); + VERIFY_SCAN_WINDOW(3, scan_window); + + EXEC(if_gatt->client->set_scan_parameters, scan_interval, scan_window); +} + +/* enable multi advertising */ +static void multi_adv_enable_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void multi_adv_enable_p(int argc, const char **argv) +{ + int client_if; + int min_interval, max_interval; + int adv_type; + int chnl_map; + int tx_power; + int timeout_s; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_MIN_INTERVAL(3, min_interval); + VERIFY_MAX_INTERVAL(4, max_interval); + VERIFY_ADV_TYPE(5, adv_type); + VERIFY_CHNL_MAP(6, chnl_map); + VERIFY_TX_POWER(7, tx_power); + VERIFY_TIMEOUT_S(8, timeout_s); + + EXEC(if_gatt->client->multi_adv_enable, client_if, min_interval, + max_interval, adv_type, chnl_map, tx_power, timeout_s); +} + +/* update multi advertiser */ +static void multi_adv_update_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void multi_adv_update_p(int argc, const char **argv) +{ + int client_if; + int min_interval, max_interval; + int adv_type; + int chnl_map; + int tx_power; + int timeout_s; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_MIN_INTERVAL(3, min_interval); + VERIFY_MAX_INTERVAL(4, max_interval); + VERIFY_ADV_TYPE(5, adv_type); + VERIFY_CHNL_MAP(6, chnl_map); + VERIFY_TX_POWER(7, tx_power); + VERIFY_TIMEOUT_S(8, timeout_s); + + EXEC(if_gatt->client->multi_adv_update, client_if, min_interval, + max_interval, adv_type, chnl_map, tx_power, timeout_s); +} + +/* set advertising data */ +static void multi_adv_set_inst_data_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void multi_adv_set_inst_data_p(int argc, const char **argv) +{ + int client_if; + bool set_scan_rsp; + bool include_name, include_txpower; + int appearance; + uint16_t manufacturer_len; + uint8_t manufacturer_data[100]; + uint16_t service_data_len; + uint8_t service_data[100]; + uint16_t service_uuid_len; + uint8_t service_uuid[100]; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + + /* set scan response */ + if (argc >= 4) + set_scan_rsp = strtol(argv[3], NULL, 0); + /* include name */ + if (argc >= 5) + include_name = strtol(argv[4], NULL, 0); + /* include txpower */ + if (argc >= 6) + include_txpower = strtol(argv[5], NULL, 0); + + VERIFY_APPEARANCE(6, appearance); + GET_VERIFY_HEX_STRING(7, manufacturer_data, manufacturer_len); + GET_VERIFY_HEX_STRING(8, service_data, service_data_len); + GET_VERIFY_HEX_STRING(9, service_uuid, service_uuid_len); + + EXEC(if_gatt->client->multi_adv_set_inst_data, client_if, set_scan_rsp, + include_name, include_txpower, appearance, manufacturer_len, + (char *) manufacturer_data, service_data_len, + (char *) service_data, service_uuid_len, (char *) service_uuid); +} + +/* multi advertising disable */ +static void multi_adv_disable_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void multi_adv_disable_p(int argc, const char **argv) +{ + int client_if; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + + EXEC(if_gatt->client->multi_adv_disable, client_if); +} + +/* batch scanconfigure storage */ +static void batchscan_cfg_storage_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void batchscan_cfg_storage_p(int argc, const char **argv) +{ + int client_if; + int batch_scan_full_max; + int batch_scan_trunc_max; + int batch_scan_notify_threshold; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_BATCH_SCAN_FULL_MAX(3, batch_scan_full_max); + VERIFY_BATCH_SCAN_TRUNC_MAX(4, batch_scan_trunc_max); + VERIFY_BATCH_SCAN_NOTIFY_THR(5, batch_scan_notify_threshold); + + EXEC(if_gatt->client->batchscan_cfg_storage, client_if, + batch_scan_full_max, batch_scan_trunc_max, + batch_scan_notify_threshold); +} + +/* enable batch scan */ +static void batchscan_enb_batch_scan_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void batchscan_enb_batch_scan_p(int argc, const char **argv) +{ + int client_if; + int scan_mode, scan_interval, scan_window; + int addr_type; + int discard_rule; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_SCAN_MODE(3, scan_mode); + VERIFY_SCAN_INTERVAL(4, scan_interval); + VERIFY_SCAN_WINDOW(5, scan_window); + VERIFY_ADDR_TYPE(6, addr_type); + VERIFY_DISCARD_RULE(7, discard_rule); + + EXEC(if_gatt->client->batchscan_enb_batch_scan, client_if, scan_mode, + scan_interval, scan_window, addr_type, discard_rule); +} + +/* batchscan disable */ +static void batchscan_dis_batch_scan_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void batchscan_dis_batch_scan_p(int argc, const char **argv) +{ + int client_if; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + + EXEC(if_gatt->client->batchscan_dis_batch_scan, client_if); +} + +/* batchscan read reports */ +static void batchscan_read_reports_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 2) { + *user = client_if_str; + *enum_func = enum_one_string; + } +} + +static void batchscan_read_reports_p(int argc, const char **argv) +{ + int client_if; + int scan_mode; + + RETURN_IF_NULL(if_gatt); + VERIFY_CLIENT_IF(2, client_if); + VERIFY_SCAN_MODE(3, scan_mode); + + EXEC(if_gatt->client->batchscan_read_reports, client_if, scan_mode); +} +#endif + +/* get_device_type */ + +static void get_device_type_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) + *enum_func = enum_devices; +} + +static void get_device_type_p(int argc, const char **argv) +{ + bt_bdaddr_t bd_addr; + int dev_type; + + RETURN_IF_NULL(if_gatt); + VERIFY_ADDR_ARG(2, &bd_addr); + + dev_type = if_gatt->client->get_device_type(&bd_addr); + haltest_info("%s: %d\n", "get_device_type", dev_type); +} + +/* test_command */ + +static void test_command_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 4) + *enum_func = enum_devices; +} + +static void test_command_p(int argc, const char **argv) +{ + int command; + int i; + bt_bdaddr_t bd_addr; + bt_uuid_t uuid; + btgatt_test_params_t params = { + .bda1 = &bd_addr, + .uuid1 = &uuid + }; + uint16_t *u = ¶ms.u1; + + RETURN_IF_NULL(if_gatt); + + /* command */ + if (argc <= 2) { + haltest_error("No command specified\n"); + return; + } + command = strtol(argv[2], NULL, 0); + + VERIFY_ADDR_ARG(3, &bd_addr); + VERIFY_UUID(4, &uuid); + + for (i = 5; i < argc; i++) + VERIFY_TEST_ARG(i, *u++); + + EXEC(if_gatt->client->test_command, command, ¶ms); +} + +static struct method client_methods[] = { + STD_METHODH(register_client, "[<uuid>]"), + STD_METHODCH(unregister_client, "<client_if>"), +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + STD_METHODCH(scan, "[1|0]"), + STD_METHODCH(connect, "<client_if> <addr> [<is_direct>] [<transport]"), + STD_METHODCH(scan_filter_param_setup, "<client_if> <action>" + " <filt_index> <feat_seln> <list_logic_type>" + " <filt_logic_type> <rssi_high_thres> <rssi_low_thres>" + " <dely_mode> <found_timeout> <lost_timeout>" + " <found_timeout_cnt>"), + STD_METHODCH(scan_filter_add_remove, "<client_if> <action> <filt_type>" + " <filt_index> <company_id> <company_id_mask>" + " [<p_uuid>] <p_uuid_mask> <addr> <addr_type>" + " [<p_data>] [<p_mask>]"), + STD_METHODCH(scan_filter_clear, "<client_if> <filt_index>"), + STD_METHODCH(scan_filter_enable, "<client_if> [<enable>]"), + STD_METHODCH(set_adv_data, "<client_if> [<set_scan_rsp>] <include_name>" + " [<include_txpower>] <min_interval> <max_interval>" + " <appearance> [<manufacturer_data>] [<service_data>]" + " [<service_uuid>]"), + STD_METHODCH(configure_mtu, "<conn_id> <mtu>"), + STD_METHODCH(conn_parameter_update, "<bd_addr> <min_interval>" + " <max_interval> <latency> <timeout>"), + STD_METHODH(set_scan_parameters, "<scan_inverval> <scan_window>"), + STD_METHODCH(multi_adv_enable, "<client_if> <min_interval>" + " <max_interval> <adv_type> <chnl_map> <tx_power>" + " <timeout_s>"), + STD_METHODCH(multi_adv_update, "<client_if> <min_interval>" + " <max_interval> <adv_type> <chnl_map> <tx_power>" + " <timeout_s>"), + STD_METHODCH(multi_adv_set_inst_data, "<client_if> [<set_scan_rsp>]" + " <include_name> [<include_txpower>] <appearance>" + " [<manufacturer_data>] [<service_data>]" + " [<service_uuid>]"), + STD_METHODCH(multi_adv_disable, "<client_if>"), + STD_METHODCH(batchscan_cfg_storage, "<client_if> <batch_scan_full_max>" + " <batch_scan_trunc_max>" + " <batch_scan_notify_threshold>"), + STD_METHODCH(batchscan_enb_batch_scan, "<client_if> <scan_mode>" + " <scan_interval> <scan_window> <addr_type>" + " <discard_rule>"), + STD_METHODCH(batchscan_dis_batch_scan, "<client_if>"), + STD_METHODCH(batchscan_read_reports, "<client_if> <scan_mode>"), +#else + STD_METHODCH(scan, "<client_if> [1|0]"), + STD_METHODCH(connect, "<client_if> <addr> [<is_direct>]"), +#endif + STD_METHODCH(disconnect, "<client_if> <addr> <conn_id>"), + STD_METHODCH(refresh, "<client_if> <addr>"), + STD_METHODCH(search_service, "<conn_id> [<uuid>]"), + STD_METHODCH(get_included_service, "<conn_id> <srvc_id>"), + STD_METHODCH(get_characteristic, "<conn_id> <srvc_id>"), + STD_METHODCH(get_descriptor, "<conn_id> <srvc_id> <char_id>"), + STD_METHODCH(read_characteristic, + "<conn_id> <srvc_id> <char_id> [<auth_req>]"), + STD_METHODCH(write_characteristic, + "<conn_id> <srvc_id> <char_id> <write_type> <hex_value> [<auth_req>]"), + STD_METHODCH(read_descriptor, + "<conn_id> <srvc_id> <char_id> <descr_id> [<auth_req>]"), + STD_METHODCH(write_descriptor, + "<conn_id> <srvc_id> <char_id> <descr_id> <write_type> <hex_value> [<auth_req>]"), + STD_METHODCH(execute_write, "<conn_id> <execute>"), + STD_METHODCH(register_for_notification, + "<client_if> <addr> <srvc_id> <char_id>"), + STD_METHODCH(deregister_for_notification, + "<client_if> <addr> <srvc_id> <char_id>"), + STD_METHODCH(read_remote_rssi, "<client_if> <addr>"), + STD_METHODCH(get_device_type, "<addr>"), + STD_METHODCH(test_command, + "<cmd> <addr> <uuid> [u1] [u2] [u3] [u4] [u5]"), + STD_METHODCH(listen, "<client_if> [1|0]"), + END_METHOD +}; + +const struct interface gatt_client_if = { + .name = "gattc", + .methods = client_methods +}; + +/* gatt server methods */ + +/* register_server */ + +static void gatts_register_server_p(int argc, const char *argv[]) +{ + bt_uuid_t uuid; + + RETURN_IF_NULL(if_gatt); + + /* uuid */ + if (argc <= 2) + gatt_str2bt_uuid_t("bed4babe", -1, &uuid); + else + gatt_str2bt_uuid_t(argv[2], -1, &uuid); + + EXEC(if_gatt->server->register_server, &uuid); +} + +/* unregister_server */ + +static void gatts_unregister_server_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = server_if_str; + *enum_func = enum_one_string; + } +} + +static void gatts_unregister_server_p(int argc, const char *argv[]) +{ + int server_if; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + + EXEC(if_gatt->server->unregister_server, server_if); +} + +/* connect */ + +static void gatts_connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = server_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = NULL; + *enum_func = enum_devices; + } +} + +static void gatts_connect_p(int argc, const char *argv[]) +{ + int server_if; + bt_bdaddr_t bd_addr; + int is_direct = 1; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + int transport = 1; +#endif + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_ADDR_ARG(3, &bd_addr); + + /* is_direct */ + if (argc > 4) + is_direct = strtol(argv[4], NULL, 0); + +#if ANDROID_VERSION < PLATFORM_VER(5, 0, 0) + EXEC(if_gatt->server->connect, server_if, &bd_addr, is_direct); +#else + /* transport */ + if (argc > 5) + transport = strtol(argv[5], NULL, 0); + + EXEC(if_gatt->server->connect, server_if, &bd_addr, is_direct, + transport); +#endif +} + +/* disconnect */ + +static void gatts_disconnect_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = server_if_str; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = last_addr; + *enum_func = enum_one_string; + } else if (argc == 5) { + *user = conn_id_str; + *enum_func = enum_one_string; + } +} + +static void gatts_disconnect_p(int argc, const char *argv[]) +{ + int server_if; + bt_bdaddr_t bd_addr; + int conn_id; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_ADDR_ARG(3, &bd_addr); + VERIFY_CONN_ID(4, conn_id); + + EXEC(if_gatt->server->disconnect, server_if, &bd_addr, conn_id); +} + +/* add_service */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_add_service_c gatts_unregister_server_c + +static void gatts_add_service_p(int argc, const char *argv[]) +{ + int server_if; + btgatt_srvc_id_t srvc_id; + int num_handles; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SRVC_ID(3, &srvc_id); + + /* num handles */ + if (argc <= 4) { + haltest_error("No num_handles specified\n"); + return; + } + num_handles = strtol(argv[4], NULL, 0); + + EXEC(if_gatt->server->add_service, server_if, &srvc_id, num_handles); +} + +/* add_included_service */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_add_included_service_c gatts_unregister_server_c + +static void gatts_add_included_service_p(int argc, const char *argv[]) +{ + int server_if; + int service_handle; + int included_handle; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SERVICE_HANDLE(3, service_handle); + VERIFY_HANDLE(4, included_handle); + + EXEC(if_gatt->server->add_included_service, server_if, service_handle, + included_handle); +} + +/* add_characteristic */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_add_characteristic_c gatts_unregister_server_c + +static void gatts_add_characteristic_p(int argc, const char *argv[]) +{ + int server_if; + int service_handle; + int properties; + int permissions; + bt_uuid_t uuid; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SERVICE_HANDLE(3, service_handle); + VERIFY_UUID(4, &uuid); + + /* properties */ + if (argc <= 5) { + haltest_error("No properties specified\n"); + return; + } + properties = strtol(argv[5], NULL, 0); + + /* permissions */ + if (argc <= 6) { + haltest_error("No permissions specified\n"); + return; + } + permissions = strtol(argv[6], NULL, 0); + + EXEC(if_gatt->server->add_characteristic, server_if, service_handle, + &uuid, properties, permissions); +} + +/* add_descriptor */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_add_descriptor_c gatts_unregister_server_c + +static void gatts_add_descriptor_p(int argc, const char *argv[]) +{ + int server_if; + int service_handle; + int permissions; + bt_uuid_t uuid; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SERVICE_HANDLE(3, service_handle); + VERIFY_UUID(4, &uuid); + + /* permissions */ + if (argc <= 5) { + haltest_error("No permissions specified\n"); + return; + } + permissions = strtol(argv[5], NULL, 0); + + EXEC(if_gatt->server->add_descriptor, server_if, service_handle, &uuid, + permissions); +} + +/* start_service */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_start_service_c gatts_unregister_server_c + +static void gatts_start_service_p(int argc, const char *argv[]) +{ + int server_if; + int service_handle; + int transport; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SERVICE_HANDLE(3, service_handle); + + /* transport */ + if (argc <= 4) { + haltest_error("No transport specified\n"); + return; + } + transport = strtol(argv[4], NULL, 0); + + EXEC(if_gatt->server->start_service, server_if, service_handle, + transport); +} + +/* stop_service */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_stop_service_c gatts_unregister_server_c + +static void gatts_stop_service_p(int argc, const char *argv[]) +{ + int server_if; + int service_handle; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SERVICE_HANDLE(3, service_handle); + + EXEC(if_gatt->server->stop_service, server_if, service_handle); +} + +/* delete_service */ + +/* Same completion as gatts_unregister_server_c */ +#define gatts_delete_service_c gatts_unregister_server_c + +static void gatts_delete_service_p(int argc, const char *argv[]) +{ + int server_if; + int service_handle; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_SERVICE_HANDLE(3, service_handle); + + EXEC(if_gatt->server->delete_service, server_if, service_handle); +} + +/* send_indication */ + +static void gatts_send_indication_p(int argc, const char *argv[]) +{ + int server_if; + int attr_handle; + int conn_id; + int confirm; + char data[200]; + int len = 0; + + RETURN_IF_NULL(if_gatt); + VERIFY_SERVER_IF(2, server_if); + VERIFY_HANDLE(3, attr_handle); + VERIFY_CONN_ID(4, conn_id); + + /* confirm */ + if (argc <= 5) { + haltest_error("No transport specified\n"); + return; + } + confirm = strtol(argv[5], NULL, 0); + + GET_VERIFY_HEX_STRING(6, data, len); + + EXEC(if_gatt->server->send_indication, server_if, attr_handle, conn_id, + len, confirm, data); +} + +/* send_response */ + +static void gatts_send_response_p(int argc, const char *argv[]) +{ + int conn_id; + int trans_id; + int status; + btgatt_response_t data; + + memset(&data, 0, sizeof(data)); + + RETURN_IF_NULL(if_gatt); + + VERIFY_CONN_ID(2, conn_id); + VERIFY_TRANS_ID(3, trans_id); + VERIFY_STATUS(4, status); + VERIFY_HANDLE(5, data.attr_value.handle); + VERIFY_OFFSET(6, data.attr_value.offset); + + data.attr_value.auth_req = 0; + data.attr_value.len = 0; + + GET_VERIFY_HEX_STRING(7, data.attr_value.value, data.attr_value.len); + + haltest_info("conn_id %d, trans_id %d, status %d", conn_id, trans_id, + status); + + EXEC(if_gatt->server->send_response, conn_id, trans_id, status, &data); +} + +#define GATTS_METHODH(n, h) METHOD(#n, gatts_##n##_p, NULL, h) +#define GATTS_METHODCH(n, h) METHOD(#n, gatts_##n##_p, gatts_##n##_c, h) + +static struct method server_methods[] = { + GATTS_METHODH(register_server, "[<uuid>]"), + GATTS_METHODCH(unregister_server, "<server_if>"), +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + GATTS_METHODCH(connect, "<server_if> <addr> [<is_direct>] [<transport>]"), +#else + GATTS_METHODCH(connect, "<server_if> <addr> [<is_direct>]"), +#endif + GATTS_METHODCH(disconnect, "<server_if> <addr> <conn_id>"), + GATTS_METHODCH(add_service, "<server_if> <srvc_id> <num_handles>"), + GATTS_METHODCH(add_included_service, + "<server_if> <service_handle> <included_handle>"), + GATTS_METHODCH(add_characteristic, + "<server_if> <service_handle> <uuid> <properites> <permissions>"), + GATTS_METHODCH(add_descriptor, + "<server_if> <service_handle> <uuid> <permissions>"), + GATTS_METHODCH(start_service, + "<server_if> <service_handle> <transport>"), + GATTS_METHODCH(stop_service, "<server_if> <service_handle>"), + GATTS_METHODCH(delete_service, "<server_if> <service_handle>"), + GATTS_METHODH(send_indication, + "<server_if> <attr_handle> <conn_id> <confirm> [<data>]"), + GATTS_METHODH(send_response, + "<conn_id> <trans_id> <status> <handle> <offset> [<data>]"), + END_METHOD +}; + +const struct interface gatt_server_if = { + .name = "gatts", + .methods = server_methods +};
diff --git a/android/client/if-hf-client.c b/android/client/if-hf-client.c new file mode 100644 index 0000000..3f6f7c2 --- /dev/null +++ b/android/client/if-hf-client.c
@@ -0,0 +1,668 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include "if-main.h" +#include "../hal-utils.h" + +const bthf_client_interface_t *if_hf_client = NULL; + +static char last_addr[MAX_ADDR_STR_LEN]; + +SINTMAP(bthf_client_connection_state_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CONNECTION_STATE_DISCONNECTED), + DELEMENT(BTHF_CLIENT_CONNECTION_STATE_CONNECTING), + DELEMENT(BTHF_CLIENT_CONNECTION_STATE_CONNECTED), + DELEMENT(BTHF_CLIENT_CONNECTION_STATE_SLC_CONNECTED), + DELEMENT(BTHF_CLIENT_CONNECTION_STATE_DISCONNECTING), +ENDMAP + +SINTMAP(bthf_client_audio_state_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_AUDIO_STATE_DISCONNECTED), + DELEMENT(BTHF_CLIENT_AUDIO_STATE_CONNECTING), + DELEMENT(BTHF_CLIENT_AUDIO_STATE_CONNECTED), + DELEMENT(BTHF_CLIENT_AUDIO_STATE_CONNECTED_MSBC), +ENDMAP + +SINTMAP(bthf_client_vr_state_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_VR_STATE_STOPPED), + DELEMENT(BTHF_CLIENT_VR_STATE_STARTED), +ENDMAP + +SINTMAP(bthf_client_network_state_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_NETWORK_STATE_NOT_AVAILABLE), + DELEMENT(BTHF_CLIENT_NETWORK_STATE_AVAILABLE), +ENDMAP + +SINTMAP(bthf_client_service_type_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_SERVICE_TYPE_HOME), + DELEMENT(BTHF_CLIENT_SERVICE_TYPE_ROAMING), +ENDMAP + +SINTMAP(bthf_client_call_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALL_NO_CALLS_IN_PROGRESS), + DELEMENT(BTHF_CLIENT_CALL_CALLS_IN_PROGRESS), +ENDMAP + +SINTMAP(bthf_client_callsetup_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALLSETUP_NONE), + DELEMENT(BTHF_CLIENT_CALLSETUP_INCOMING), + DELEMENT(BTHF_CLIENT_CALLSETUP_OUTGOING), + DELEMENT(BTHF_CLIENT_CALLSETUP_ALERTING), +ENDMAP + +SINTMAP(bthf_client_callheld_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALLHELD_NONE), + DELEMENT(BTHF_CLIENT_CALLHELD_HOLD_AND_ACTIVE), + DELEMENT(BTHF_CLIENT_CALLHELD_HOLD), +ENDMAP + +SINTMAP(bthf_client_resp_and_hold_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_RESP_AND_HOLD_HELD), + DELEMENT(BTRH_CLIENT_RESP_AND_HOLD_ACCEPT), + DELEMENT(BTRH_CLIENT_RESP_AND_HOLD_REJECT), +ENDMAP + +SINTMAP(bthf_client_call_direction_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALL_DIRECTION_OUTGOING), + DELEMENT(BTHF_CLIENT_CALL_DIRECTION_INCOMING), +ENDMAP + +SINTMAP(bthf_client_call_state_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALL_STATE_ACTIVE), + DELEMENT(BTHF_CLIENT_CALL_STATE_HELD), + DELEMENT(BTHF_CLIENT_CALL_STATE_DIALING), + DELEMENT(BTHF_CLIENT_CALL_STATE_ALERTING), + DELEMENT(BTHF_CLIENT_CALL_STATE_INCOMING), + DELEMENT(BTHF_CLIENT_CALL_STATE_WAITING), + DELEMENT(BTHF_CLIENT_CALL_STATE_HELD_BY_RESP_HOLD), +ENDMAP + +SINTMAP(bthf_client_call_mpty_type_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALL_MPTY_TYPE_SINGLE), + DELEMENT(BTHF_CLIENT_CALL_MPTY_TYPE_MULTI), +ENDMAP + +SINTMAP(bthf_client_volume_type_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_VOLUME_TYPE_SPK), + DELEMENT(BTHF_CLIENT_VOLUME_TYPE_MIC), +ENDMAP + +SINTMAP(bthf_client_cmd_complete_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_OK), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR_NO_CARRIER), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR_BUSY), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR_NO_ANSWER), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR_DELAYED), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR_BLACKLISTED), + DELEMENT(BTHF_CLIENT_CMD_COMPLETE_ERROR_CME), +ENDMAP + +SINTMAP(bthf_client_subscriber_service_type_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_SERVICE_UNKNOWN), + DELEMENT(BTHF_CLIENT_SERVICE_VOICE), + DELEMENT(BTHF_CLIENT_SERVICE_FAX), +ENDMAP + +SINTMAP(bthf_client_in_band_ring_state_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_IN_BAND_RINGTONE_NOT_PROVIDED), + DELEMENT(BTHF_CLIENT_IN_BAND_RINGTONE_PROVIDED), +ENDMAP + +SINTMAP(bthf_client_call_action_t, -1, "(unknown)") + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_0), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_1), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_2), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_3), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_4), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_1x), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHLD_2x), + DELEMENT(BTHF_CLIENT_CALL_ACTION_ATA), + DELEMENT(BTHF_CLIENT_CALL_ACTION_CHUP), + DELEMENT(BTHF_CLIENT_CALL_ACTION_BTRH_0), + DELEMENT(BTHF_CLIENT_CALL_ACTION_BTRH_1), + DELEMENT(BTHF_CLIENT_CALL_ACTION_BTRH_2), +ENDMAP + +/* Callbacks */ + +static char features_str[512]; + +static const char *pear_features_t2str(int feat) +{ + memset(features_str, 0, sizeof(features_str)); + + sprintf(features_str, "BTHF_CLIENT_PEER_FEAT_3WAY: %s,\n" + "BTHF_CLIENT_PEER_FEAT_ECNR: %s,\n" + "BTHF_CLIENT_PEER_FEAT_VREC: %s,\n" + "BTHF_CLIENT_PEER_FEAT_INBAND: %s,\n" + "BTHF_CLIENT_PEER_FEAT_VTAG: %s,\n" + "BTHF_CLIENT_PEER_FEAT_REJECT: %s,\n" + "BTHF_CLIENT_PEER_FEAT_ECS: %s,\n" + "BTHF_CLIENT_PEER_FEAT_ECC: %s,\n" + "BTHF_CLIENT_PEER_FEAT_EXTERR: %s,\n" + "BTHF_CLIENT_PEER_FEAT_CODEC: %s,\n", + feat & BTHF_CLIENT_PEER_FEAT_3WAY ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_ECNR ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_VREC ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_INBAND ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_VTAG ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_REJECT ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_ECS ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_ECC ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_EXTERR ? "True" : "False", + feat & BTHF_CLIENT_PEER_FEAT_CODEC ? "True" : "False"); + + return features_str; +} + +static const char *chld_features_t2str(int feat) +{ + memset(features_str, 0, sizeof(features_str)); + + sprintf(features_str, + "BTHF_CLIENT_CHLD_FEAT_REL: %s,\n" + "BTHF_CLIENT_CHLD_FEAT_REL_ACC: %s,\n" + "BTHF_CLIENT_CHLD_FEAT_REL_X: %s,\n" + "BTHF_CLIENT_CHLD_FEAT_HOLD_ACC: %s,\n" + "BTHF_CLIENT_CHLD_FEAT_PRIV_X: %s,\n" + "BTHF_CLIENT_CHLD_FEAT_MERGE: %s,\n" + "BTHF_CLIENT_CHLD_FEAT_MERGE_DETACH: %s,\n", + feat & BTHF_CLIENT_CHLD_FEAT_REL ? "True" : "False", + feat & BTHF_CLIENT_CHLD_FEAT_REL_ACC ? "True" : "False", + feat & BTHF_CLIENT_CHLD_FEAT_REL_X ? "True" : "False", + feat & BTHF_CLIENT_CHLD_FEAT_HOLD_ACC ? "True" : "False", + feat & BTHF_CLIENT_CHLD_FEAT_PRIV_X ? "True" : "False", + feat & BTHF_CLIENT_CHLD_FEAT_MERGE ? "True" : "False", + feat & BTHF_CLIENT_CHLD_FEAT_MERGE_DETACH ? "True" : "False"); + + return features_str; +} + +/* Callback for connection state change. */ +static void hf_client_connection_state_callback( + bthf_client_connection_state_t state, + unsigned int peer_feat, + unsigned int chld_feat, + bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: state=%s bd_addr=%s\n", __func__, + bthf_client_connection_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); + + if (state != BTHF_CLIENT_CONNECTION_STATE_CONNECTED) + return; + + haltest_info("\tpeer_features%s\n", pear_features_t2str(peer_feat)); + haltest_info("\tchld_feat=%s\n", chld_features_t2str(chld_feat)); +} + +/* Callback for audio connection state change. */ +static void hf_client_audio_state_callback(bthf_client_audio_state_t state, + bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: state=%s bd_addr=%s\n", __func__, + bthf_client_audio_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +/* Callback for VR connection state change. */ +static void hf_client_vr_cmd_callback(bthf_client_vr_state_t state) +{ + haltest_info("%s: vr_state=%s\n", __func__, + bthf_client_vr_state_t2str(state)); +} + +/* Callback for network state change */ +static void hf_client_network_state_callback(bthf_client_network_state_t state) +{ + haltest_info("%s: network_state=%s\n", __func__, + bthf_client_network_state_t2str(state)); +} + +/* Callback for network roaming status change */ +static void hf_client_network_roaming_callback(bthf_client_service_type_t type) +{ + haltest_info("%s: service_type=%s\n", __func__, + bthf_client_service_type_t2str(type)); +} + +/* Callback for signal strength indication */ +static void hf_client_network_signal_callback(int signal_strength) +{ + haltest_info("%s: signal strength=%d\n", __func__, signal_strength); +} + +/* Callback for battery level indication */ +static void hf_client_battery_level_callback(int battery_level) +{ + haltest_info("%s: battery_lvl=%d\n", __func__, battery_level); +} + +/* Callback for current operator name */ +static void hf_client_current_operator_callback(const char *name) +{ + haltest_info("%s: operator_name=%s\n", __func__, name); +} + +/* Callback for call indicator */ +static void hf_client_call_callback(bthf_client_call_t call) +{ + haltest_info("%s: call_state=%s\n", __func__, + bthf_client_call_t2str(call)); +} + +/* Callback for callsetup indicator */ +static void hf_client_callsetup_callback(bthf_client_callsetup_t callsetup) +{ + haltest_info("%s: callsetup=%s\n", __func__, + bthf_client_callsetup_t2str(callsetup)); +} + +/* Callback for callheld indicator */ +static void hf_client_callheld_callback(bthf_client_callheld_t callheld) +{ + haltest_info("%s: callheld=%s\n", __func__, + bthf_client_callheld_t2str(callheld)); +} + +/* Callback for response and hold */ +static void hf_client_resp_and_hold_callback( + bthf_client_resp_and_hold_t resp_and_hold) +{ + haltest_info("%s: resp_and_hold=%s\n", __func__, + bthf_client_resp_and_hold_t2str(resp_and_hold)); +} + +/* Callback for Calling Line Identification notification */ +static void hf_client_clip_callback(const char *number) +{ + haltest_info("%s: number=%s\n", __func__, number); +} + +/* Callback for Call Waiting notification */ +static void hf_client_call_waiting_callback(const char *number) +{ + haltest_info("%s: number=%s\n", __func__, number); +} + +/* Callback for listing current calls. Can be called multiple time. */ +static void hf_client_current_calls_callback(int index, + bthf_client_call_direction_t dir, + bthf_client_call_state_t state, + bthf_client_call_mpty_type_t mpty, + const char *number) +{ + haltest_info("%s: index=%d, direction=%s, state=%s, m_party=%s\n", + __func__, index, + bthf_client_call_direction_t2str(dir), + bthf_client_call_state_t2str(state), + bthf_client_call_mpty_type_t2str(mpty)); + + if (number) + haltest_info("%s: number=%s\n", __func__, number); +} + +/* Callback for audio volume change */ +static void hf_client_volume_change_callback(bthf_client_volume_type_t type, + int volume) +{ + haltest_info("%s: vol_type=%s, value=%d\n", __func__, + bthf_client_volume_type_t2str(type), volume); +} + +/* Callback for command complete event */ +static void hf_client_cmd_complete_callback(bthf_client_cmd_complete_t type, + int cme) +{ + haltest_info("%s: type=%s, cme=%d\n", __func__, + bthf_client_cmd_complete_t2str(type), cme); +} + +/* Callback for subscriber information */ +static void hf_client_subscriber_info_callback(const char *name, + bthf_client_subscriber_service_type_t type) +{ + haltest_info("%s: name=%s, type=%s\n", __func__, name, + bthf_client_subscriber_service_type_t2str(type)); +} + +/* Callback for in-band ring tone settings */ +static void hf_client_in_band_ring_tone_callback( + bthf_client_in_band_ring_state_t state) +{ + haltest_info("%s: state=%s\n", __func__, + bthf_client_in_band_ring_state_t2str(state)); +} + +/* Callback for requested number from AG */ +static void hf_client_last_voice_tag_number_callback(const char *number) +{ + haltest_info("%s: number=%s\n", __func__, number); +} + +/* Callback for sending ring indication to app */ +static void hf_client_ring_indication_callback(void) +{ + haltest_info("%s\n", __func__); +} + +static bthf_client_callbacks_t hf_client_cbacks = { + .size = sizeof(hf_client_cbacks), + .connection_state_cb = hf_client_connection_state_callback, + .audio_state_cb = hf_client_audio_state_callback, + .vr_cmd_cb = hf_client_vr_cmd_callback, + .network_state_cb = hf_client_network_state_callback, + .network_roaming_cb = hf_client_network_roaming_callback, + .network_signal_cb = hf_client_network_signal_callback, + .battery_level_cb = hf_client_battery_level_callback, + .current_operator_cb = hf_client_current_operator_callback, + .call_cb = hf_client_call_callback, + .callsetup_cb = hf_client_callsetup_callback, + .callheld_cb = hf_client_callheld_callback, + .resp_and_hold_cb = hf_client_resp_and_hold_callback, + .clip_cb = hf_client_clip_callback, + .call_waiting_cb = hf_client_call_waiting_callback, + .current_calls_cb = hf_client_current_calls_callback, + .volume_change_cb = hf_client_volume_change_callback, + .cmd_complete_cb = hf_client_cmd_complete_callback, + .subscriber_info_cb = hf_client_subscriber_info_callback, + .in_band_ring_tone_cb = hf_client_in_band_ring_tone_callback, + .last_voice_tag_number_callback = + hf_client_last_voice_tag_number_callback, + .ring_indication_cb = hf_client_ring_indication_callback, +}; + +/* init */ +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->init, &hf_client_cbacks); +} + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +/* connect to audio gateway */ +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf_client); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf_client->connect, &addr); +} + +/* + * This completion function will be used for several methods + * returning recently connected address + */ +static void connected_addr_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +/* Map completion to connected_addr_c */ +#define disconnect_c connected_addr_c + +/* disconnect from audio gateway */ +static void disconnect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf_client); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf_client->disconnect, &addr); +} + +static void connect_audio_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +/* create an audio connection */ +static void connect_audio_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf_client); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf_client->connect_audio, &addr); +} + +/* Map completion to connected_addr_c */ +#define disconnect_audio_c connected_addr_c + +/* close the audio connection */ +static void disconnect_audio_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf_client); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf_client->disconnect_audio, &addr); +} + +/* start voice recognition */ +static void start_voice_recognition_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->start_voice_recognition); +} + +/* stop voice recognition */ +static void stop_voice_recognition_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->stop_voice_recognition); +} + +static void volume_control_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(bthf_client_volume_type_t); + *enum_func = enum_defines; + } +} + +/* volume control */ +static void volume_control_p(int argc, const char **argv) +{ + bthf_client_volume_type_t type; + int volume; + + RETURN_IF_NULL(if_hf_client); + + /* volume type */ + if (argc <= 2) { + haltest_error("No volume type specified\n"); + return; + } + type = str2bthf_client_volume_type_t(argv[2]); + + /* volume */ + if (argc <= 3) { + haltest_error("No volume specified\n"); + return; + } + volume = atoi(argv[3]); + + EXEC(if_hf_client->volume_control, type, volume); +} + +/* place a call with number a number */ +static void dial_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + /* number string */ + if (argc <= 2) { + haltest_info("Number not specified. Redial\n"); + EXEC(if_hf_client->dial, NULL); + return; + } + + EXEC(if_hf_client->dial, argv[2]); +} + +/* place a call with number specified by location (speed dial) */ +static void dial_memory_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + /* memory index */ + if (argc <= 2) { + haltest_error("No memory index specified\n"); + return; + } + + EXEC(if_hf_client->dial_memory, atoi(argv[2])); +} + +static void handle_call_action_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(bthf_client_call_action_t); + *enum_func = enum_defines; + } +} + +/* perform specified call related action */ +static void handle_call_action_p(int argc, const char **argv) +{ + bthf_client_call_action_t action; + int index = 0; + + RETURN_IF_NULL(if_hf_client); + + /* action */ + if (argc <= 2) { + haltest_error("No action specified\n"); + return; + } + action = str2bthf_client_call_action_t(argv[2]); + + /* call index */ + if (action == BTHF_CLIENT_CALL_ACTION_CHLD_1x || + action == BTHF_CLIENT_CALL_ACTION_CHLD_2x) { + if (argc <= 3) { + haltest_error("No call index specified\n"); + return; + } + index = atoi(argv[3]); + } + + EXEC(if_hf_client->handle_call_action, action, index); +} + +/* query list of current calls */ +static void query_current_calls_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->query_current_calls); +} + +/* query name of current selected operator */ +static void query_current_operator_name_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->query_current_operator_name); +} + +/* Retrieve subscriber information */ +static void retrieve_subscriber_info_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->retrieve_subscriber_info); +} + +/* Send DTMF code*/ +static void send_dtmf_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->send_dtmf, *argv[2]); +} + +/* Request a phone number from AG corresponding to last voice tag recorded */ +static void request_last_voice_tag_number_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXEC(if_hf_client->request_last_voice_tag_number); +} + +/* Closes the interface. */ +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf_client); + + EXECV(if_hf_client->cleanup); + if_hf_client = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(connect, "<addr>"), + STD_METHODCH(disconnect, "<addr>"), + STD_METHODCH(connect_audio, "<addr>"), + STD_METHODCH(disconnect_audio, "<addr>"), + STD_METHOD(start_voice_recognition), + STD_METHOD(stop_voice_recognition), + STD_METHODCH(volume_control, "<volume_type> <value>"), + STD_METHODH(dial, "<destination_number>"), + STD_METHODH(dial_memory, "<memory_location>"), + STD_METHODCH(handle_call_action, "<call_action> <call_index>"), + STD_METHOD(query_current_calls), + STD_METHOD(query_current_operator_name), + STD_METHOD(retrieve_subscriber_info), + STD_METHODH(send_dtmf, "<code>"), + STD_METHOD(request_last_voice_tag_number), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface hf_client_if = { + .name = "handsfree_client", + .methods = methods +};
diff --git a/android/client/if-hf.c b/android/client/if-hf.c new file mode 100644 index 0000000..6b798f8 --- /dev/null +++ b/android/client/if-hf.c
@@ -0,0 +1,1062 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include "if-main.h" +#include "../hal-utils.h" + +const bthf_interface_t *if_hf = NULL; + +SINTMAP(bthf_at_response_t, -1, "(unknown)") + DELEMENT(BTHF_AT_RESPONSE_ERROR), + DELEMENT(BTHF_AT_RESPONSE_OK), +ENDMAP + +SINTMAP(bthf_connection_state_t, -1, "(unknown)") + DELEMENT(BTHF_CONNECTION_STATE_DISCONNECTED), + DELEMENT(BTHF_CONNECTION_STATE_CONNECTING), + DELEMENT(BTHF_CONNECTION_STATE_CONNECTED), + DELEMENT(BTHF_CONNECTION_STATE_SLC_CONNECTED), + DELEMENT(BTHF_CONNECTION_STATE_DISCONNECTING), +ENDMAP + +SINTMAP(bthf_audio_state_t, -1, "(unknown)") + DELEMENT(BTHF_AUDIO_STATE_DISCONNECTED), + DELEMENT(BTHF_AUDIO_STATE_CONNECTING), + DELEMENT(BTHF_AUDIO_STATE_CONNECTED), + DELEMENT(BTHF_AUDIO_STATE_DISCONNECTING), +ENDMAP + +SINTMAP(bthf_vr_state_t, -1, "(unknown)") + DELEMENT(BTHF_VR_STATE_STOPPED), + DELEMENT(BTHF_VR_STATE_STARTED), +ENDMAP + +SINTMAP(bthf_volume_type_t, -1, "(unknown)") + DELEMENT(BTHF_VOLUME_TYPE_SPK), + DELEMENT(BTHF_VOLUME_TYPE_MIC), +ENDMAP + +SINTMAP(bthf_nrec_t, -1, "(unknown)") + DELEMENT(BTHF_NREC_STOP), + DELEMENT(BTHF_NREC_START), +ENDMAP + +SINTMAP(bthf_chld_type_t, -1, "(unknown)") + DELEMENT(BTHF_CHLD_TYPE_RELEASEHELD), + DELEMENT(BTHF_CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD), + DELEMENT(BTHF_CHLD_TYPE_HOLDACTIVE_ACCEPTHELD), + DELEMENT(BTHF_CHLD_TYPE_ADDHELDTOCONF), +ENDMAP + +/* Network Status */ +SINTMAP(bthf_network_state_t, -1, "(unknown)") + DELEMENT(BTHF_NETWORK_STATE_NOT_AVAILABLE), + DELEMENT(BTHF_NETWORK_STATE_AVAILABLE), +ENDMAP + +/* Service type */ +SINTMAP(bthf_service_type_t, -1, "(unknown)") + DELEMENT(BTHF_SERVICE_TYPE_HOME), + DELEMENT(BTHF_SERVICE_TYPE_ROAMING), +ENDMAP + +SINTMAP(bthf_call_state_t, -1, "(unknown)") + DELEMENT(BTHF_CALL_STATE_ACTIVE), + DELEMENT(BTHF_CALL_STATE_HELD), + DELEMENT(BTHF_CALL_STATE_DIALING), + DELEMENT(BTHF_CALL_STATE_ALERTING), + DELEMENT(BTHF_CALL_STATE_INCOMING), + DELEMENT(BTHF_CALL_STATE_WAITING), + DELEMENT(BTHF_CALL_STATE_IDLE), +ENDMAP + +SINTMAP(bthf_call_direction_t, -1, "(unknown)") + DELEMENT(BTHF_CALL_DIRECTION_OUTGOING), + DELEMENT(BTHF_CALL_DIRECTION_INCOMING), +ENDMAP + +SINTMAP(bthf_call_mode_t, -1, "(unknown)") + DELEMENT(BTHF_CALL_TYPE_VOICE), + DELEMENT(BTHF_CALL_TYPE_DATA), + DELEMENT(BTHF_CALL_TYPE_FAX), +ENDMAP + +SINTMAP(bthf_call_mpty_type_t, -1, "(unknown)") + DELEMENT(BTHF_CALL_MPTY_TYPE_SINGLE), + DELEMENT(BTHF_CALL_MPTY_TYPE_MULTI), +ENDMAP + +SINTMAP(bthf_call_addrtype_t, -1, "(unknown)") + DELEMENT(BTHF_CALL_ADDRTYPE_UNKNOWN), + DELEMENT(BTHF_CALL_ADDRTYPE_INTERNATIONAL), +ENDMAP + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +SINTMAP(bthf_wbs_config_t, -1, "(unknown)") + DELEMENT(BTHF_WBS_NONE), + DELEMENT(BTHF_WBS_NO), + DELEMENT(BTHF_WBS_YES), +ENDMAP +#endif + +/* Callbacks */ + +static char last_addr[MAX_ADDR_STR_LEN]; + +/* + * Callback for connection state change. + * state will have one of the values from BtHfConnectionState + */ +static void connection_state_cb(bthf_connection_state_t state, + bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: state=%s bd_addr=%s\n", __func__, + bthf_connection_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +/* + * Callback for audio connection state change. + * state will have one of the values from BtHfAudioState + */ +static void audio_state_cb(bthf_audio_state_t state, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: state=%s bd_addr=%s\n", __func__, + bthf_audio_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +/* + * Callback for VR connection state change. + * state will have one of the values from BtHfVRState + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void vr_cmd_cb(bthf_vr_state_t state, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: state=%s bd_addr=%s\n", __func__, + bthf_vr_state_t2str(state), + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void vr_cmd_cb(bthf_vr_state_t state) +{ + haltest_info("%s: state=%s\n", __func__, bthf_vr_state_t2str(state)); +} +#endif + +/* Callback for answer incoming call (ATA) */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void answer_call_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void answer_call_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +/* Callback for disconnect call (AT+CHUP) */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void hangup_call_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void hangup_call_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +/* + * Callback for disconnect call (AT+CHUP) + * type will denote Speaker/Mic gain (BtHfVolumeControl). + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void volume_cmd_cb(bthf_volume_type_t type, int volume, + bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: type=%s volume=%d bd_addr=%s\n", __func__, + bthf_volume_type_t2str(type), volume, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void volume_cmd_cb(bthf_volume_type_t type, int volume) +{ + haltest_info("%s: type=%s volume=%d\n", __func__, + bthf_volume_type_t2str(type), volume); +} +#endif + +/* + * Callback for dialing an outgoing call + * If number is NULL, redial + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void dial_call_cmd_cb(char *number, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: number=%s bd_addr=%s\n", __func__, number, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void dial_call_cmd_cb(char *number) +{ + haltest_info("%s: number=%s\n", __func__, number); +} +#endif + +/* + * Callback for sending DTMF tones + * tone contains the dtmf character to be sent + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void dtmf_cmd_cb(char tone, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: tone=%d bd_addr=%s\n", __func__, tone, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void dtmf_cmd_cb(char tone) +{ + haltest_info("%s: tone=%d\n", __func__, tone); +} +#endif + +/* + * Callback for enabling/disabling noise reduction/echo cancellation + * value will be 1 to enable, 0 to disable + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void nrec_cmd_cb(bthf_nrec_t nrec, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: nrec=%s bd_addr=%s\n", __func__, + bthf_nrec_t2str(nrec), + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void nrec_cmd_cb(bthf_nrec_t nrec) +{ + haltest_info("%s: nrec=%s\n", __func__, bthf_nrec_t2str(nrec)); +} +#endif + +/* + * Callback for call hold handling (AT+CHLD) + * value will contain the call hold command (0, 1, 2, 3) + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void chld_cmd_cb(bthf_chld_type_t chld, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: chld=%s bd_addr=%s\n", __func__, + bthf_chld_type_t2str(chld), + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void chld_cmd_cb(bthf_chld_type_t chld) +{ + haltest_info("%s: chld=%s\n", __func__, bthf_chld_type_t2str(chld)); +} +#endif + +/* Callback for CNUM (subscriber number) */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void cnum_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void cnum_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +/* Callback for indicators (CIND) */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void cind_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void cind_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +/* Callback for operator selection (COPS) */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void cops_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void cops_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +/* Callback for call list (AT+CLCC) */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void clcc_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void clcc_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +/* + * Callback for unknown AT command recd from HF + * at_string will contain the unparsed AT string + */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void unknown_at_cmd_cb(char *at_string, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: at_string=%s bd_addr=%s\n", __func__, at_string, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void unknown_at_cmd_cb(char *at_string) +{ + haltest_info("%s: at_string=%s\n", __func__, at_string); +} +#endif + +/* Callback for keypressed (HSP) event. */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void key_pressed_cmd_cb(bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#else +static void key_pressed_cmd_cb(void) +{ + haltest_info("%s\n", __func__); +} +#endif + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void wbs_cb(bthf_wbs_config_t wbs, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr)); +} +#endif + +static bthf_callbacks_t hf_cbacks = { + .size = sizeof(hf_cbacks), + .connection_state_cb = connection_state_cb, + .audio_state_cb = audio_state_cb, + .vr_cmd_cb = vr_cmd_cb, + .answer_call_cmd_cb = answer_call_cmd_cb, + .hangup_call_cmd_cb = hangup_call_cmd_cb, + .volume_cmd_cb = volume_cmd_cb, + .dial_call_cmd_cb = dial_call_cmd_cb, + .dtmf_cmd_cb = dtmf_cmd_cb, + .nrec_cmd_cb = nrec_cmd_cb, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .wbs_cb = wbs_cb, +#endif + .chld_cmd_cb = chld_cmd_cb, + .cnum_cmd_cb = cnum_cmd_cb, + .cind_cmd_cb = cind_cmd_cb, + .cops_cmd_cb = cops_cmd_cb, + .clcc_cmd_cb = clcc_cmd_cb, + .unknown_at_cmd_cb = unknown_at_cmd_cb, + .key_pressed_cmd_cb = key_pressed_cmd_cb, +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + int max_hf_clients; +#endif + + RETURN_IF_NULL(if_hf); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + if (argc <= 2) + max_hf_clients = 1; + else + max_hf_clients = atoi(argv[2]); + + EXEC(if_hf->init, &hf_cbacks, max_hf_clients); +#else + EXEC(if_hf->init, &hf_cbacks); +#endif +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf->connect, &addr); +} + +/* disconnect */ + +/* + * This completion function will be used for several methods + * returning recently connected address + */ +static void connected_addr_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = last_addr; + *enum_func = enum_one_string; + } +} + +/* Map completion to connected_addr_c */ +#define disconnect_c connected_addr_c + +static void disconnect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf->disconnect, &addr); +} + +/* create an audio connection */ + +/* Map completion to connected_addr_c */ +#define connect_audio_c connected_addr_c + +static void connect_audio_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf->connect_audio, &addr); +} + +/* close the audio connection */ + +/* Map completion to connected_addr_c */ +#define disconnect_audio_c connected_addr_c + +static void disconnect_audio_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf->disconnect_audio, &addr); +} + +/* start voice recognition */ + +static void start_voice_recognition_p(int argc, const char **argv) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf->start_voice_recognition, &addr); +#else + EXEC(if_hf->start_voice_recognition); +#endif +} + +/* stop voice recognition */ + +static void stop_voice_recognition_p(int argc, const char **argv) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hf->stop_voice_recognition, &addr); +#else + EXEC(if_hf->stop_voice_recognition); +#endif +} + +/* volume control */ + +static void volume_control_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(bthf_volume_type_t); + *enum_func = enum_defines; + } +} + +static void volume_control_p(int argc, const char **argv) +{ + bthf_volume_type_t type; + int volume; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + + /* volume type */ + if (argc <= 2) { + haltest_error("No volume type specified\n"); + return; + } + type = str2bthf_volume_type_t(argv[2]); + + /* volume */ + if (argc <= 3) { + haltest_error("No volume specified\n"); + return; + } + volume = atoi(argv[3]); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(4, &addr); + + EXEC(if_hf->volume_control, type, volume, &addr); +#else + EXEC(if_hf->volume_control, type, volume); +#endif +} + +/* Combined device status change notification */ + +static void device_status_notification_c(int argc, const char **argv, + enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(bthf_network_state_t); + *enum_func = enum_defines; + } else if (argc == 4) { + *user = TYPE_ENUM(bthf_service_type_t); + *enum_func = enum_defines; + } +} + +static void device_status_notification_p(int argc, const char **argv) +{ + bthf_network_state_t ntk_state; + bthf_service_type_t svc_type; + int signal; + int batt_chg; + + RETURN_IF_NULL(if_hf); + + /* network state */ + if (argc <= 2) { + haltest_error("No network state specified\n"); + return; + } + ntk_state = str2bthf_network_state_t(argv[2]); + + /* service type */ + if (argc <= 3) { + haltest_error("No service type specified\n"); + return; + } + svc_type = str2bthf_service_type_t(argv[3]); + + /* signal */ + if (argc <= 4) { + haltest_error("No signal specified\n"); + return; + } + signal = atoi(argv[4]); + + /* batt_chg */ + if (argc <= 5) { + haltest_error("No batt_chg specified\n"); + return; + } + batt_chg = atoi(argv[5]); + + EXEC(if_hf->device_status_notification, ntk_state, svc_type, signal, + batt_chg); +} + +/* Response for COPS command */ + +static void cops_response_p(int argc, const char **argv) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + + /* response */ + if (argc <= 2) { + haltest_error("No cops specified\n"); + return; + } + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(3, &addr); + + EXEC(if_hf->cops_response, argv[2], &addr); +#else + EXEC(if_hf->cops_response, argv[2]); +#endif +} + +/* Response for CIND command */ + +static void cind_response_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 6) { + *user = TYPE_ENUM(bthf_call_state_t); + *enum_func = enum_defines; + } +} + +static void cind_response_p(int argc, const char **argv) +{ + int svc; + int num_active; + int num_held; + bthf_call_state_t call_setup_state; + int signal; + int roam; + int batt_chg; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + + /* svc */ + if (argc <= 2) { + haltest_error("No service specified\n"); + return; + } + svc = atoi(argv[2]); + + /* num active */ + if (argc <= 3) { + haltest_error("No num active specified\n"); + return; + } + num_active = atoi(argv[3]); + + /* num held */ + if (argc <= 4) { + haltest_error("No num held specified\n"); + return; + } + num_held = atoi(argv[4]); + + /* call setup state */ + if (argc <= 5) { + haltest_error("No call setup state specified\n"); + return; + } + call_setup_state = str2bthf_call_state_t(argv[5]); + + /* signal */ + if (argc <= 6) { + haltest_error("No signal specified\n"); + return; + } + signal = atoi(argv[6]); + + /* roam */ + if (argc <= 7) { + haltest_error("No roam specified\n"); + return; + } + roam = atoi(argv[7]); + + /* batt_chg */ + if (argc <= 8) { + haltest_error("No batt_chg specified\n"); + return; + } + batt_chg = atoi(argv[8]); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(9, &addr); + + EXEC(if_hf->cind_response, svc, num_active, num_held, call_setup_state, + signal, roam, batt_chg, &addr); +#else + EXEC(if_hf->cind_response, svc, num_active, num_held, call_setup_state, + signal, roam, batt_chg); +#endif +} + +/* Pre-formatted AT response, typically in response to unknown AT cmd */ + +static void formatted_at_response_p(int argc, const char **argv) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + + /* response */ + if (argc <= 2) { + haltest_error("No response specified\n"); + return; + } + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(3, &addr); + + EXEC(if_hf->formatted_at_response, argv[2], &addr); +#else + EXEC(if_hf->formatted_at_response, argv[2]); +#endif +} + +/* at_response */ + +static void at_response_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(bthf_at_response_t); + *enum_func = enum_defines; + } +} + +static void at_response_p(int argc, const char **argv) +{ + bthf_at_response_t response_code; + int error_code; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + + /* response type */ + if (argc <= 2) { + haltest_error("No response specified\n"); + return; + } + response_code = str2bthf_at_response_t(argv[2]); + + /* error code */ + if (argc <= 3) + error_code = 0; + else + error_code = atoi(argv[3]); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(4, &addr); + + EXEC(if_hf->at_response, response_code, error_code, &addr); +#else + EXEC(if_hf->at_response, response_code, error_code); +#endif +} + +/* response for CLCC command */ + +static void clcc_response_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 4) { + *user = TYPE_ENUM(bthf_call_direction_t); + *enum_func = enum_defines; + } else if (argc == 5) { + *user = TYPE_ENUM(bthf_call_state_t); + *enum_func = enum_defines; + } else if (argc == 6) { + *user = TYPE_ENUM(bthf_call_mode_t); + *enum_func = enum_defines; + } else if (argc == 7) { + *user = TYPE_ENUM(bthf_call_mpty_type_t); + *enum_func = enum_defines; + } else if (argc == 9) { + *user = TYPE_ENUM(bthf_call_addrtype_t); + *enum_func = enum_defines; + } +} + +static void clcc_response_p(int argc, const char **argv) +{ + int index; + bthf_call_direction_t dir; + bthf_call_state_t state; + bthf_call_mode_t mode; + bthf_call_mpty_type_t mpty; + const char *number; + bthf_call_addrtype_t type; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + bt_bdaddr_t addr; +#endif + + RETURN_IF_NULL(if_hf); + + /* index */ + if (argc <= 2) { + haltest_error("No index specified\n"); + return; + } + index = atoi(argv[2]); + + /* direction */ + if (argc <= 3) { + haltest_error("No direction specified\n"); + return; + } + dir = str2bthf_call_direction_t(argv[3]); + + /* call state */ + if (argc <= 4) { + haltest_error("No call state specified\n"); + return; + } + state = str2bthf_call_state_t(argv[4]); + + /* call mode */ + if (argc <= 5) { + haltest_error("No mode specified\n"); + return; + } + mode = str2bthf_call_mode_t(argv[5]); + + /* call mpty type */ + if (argc <= 6) { + haltest_error("No mpty type specified\n"); + return; + } + mpty = str2bthf_call_mpty_type_t(argv[6]); + + /* number */ + if (argc <= 7) { + haltest_error("No number specified\n"); + return; + } + number = argv[7]; + + /* call mpty type */ + if (argc <= 8) { + haltest_error("No address type specified\n"); + return; + } + type = str2bthf_call_addrtype_t(argv[8]); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + VERIFY_ADDR_ARG(9, &addr); + + EXEC(if_hf->clcc_response, index, dir, state, mode, mpty, number, + type, &addr); +#else + EXEC(if_hf->clcc_response, index, dir, state, mode, mpty, number, + type); +#endif +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void configure_wbs_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 4) { + *user = TYPE_ENUM(bthf_wbs_config_t); + *enum_func = enum_defines; + } +} + +static void configure_wbs_p(int argc, const char **argv) +{ + bthf_wbs_config_t wbs; + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hf); + + if (argc <= 3) { + haltest_error("Too few parameters specified\n"); + return; + } + + VERIFY_ADDR_ARG(2, &addr); + wbs = str2bthf_wbs_config_t(argv[3]); + + EXEC(if_hf->configure_wbs, &addr, wbs); +} +#endif + +/* phone state change */ + +static void phone_state_change_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 5) { + *user = TYPE_ENUM(bthf_call_state_t); + *enum_func = enum_defines; + } else if (argc == 7) { + *user = TYPE_ENUM(bthf_call_addrtype_t); + *enum_func = enum_defines; + } +} + +static void phone_state_change_p(int argc, const char **argv) +{ + int num_active; + int num_held; + bthf_call_state_t call_setup_state; + const char *number; + bthf_call_addrtype_t type; + + RETURN_IF_NULL(if_hf); + + /* num_active */ + if (argc <= 2) { + haltest_error("No num_active specified\n"); + return; + } + num_active = atoi(argv[2]); + + /* num_held */ + if (argc <= 3) { + haltest_error("No num_held specified\n"); + return; + } + num_held = atoi(argv[3]); + + /* setup state */ + if (argc <= 4) { + haltest_error("No call setup state specified\n"); + return; + } + call_setup_state = str2bthf_call_state_t(argv[4]); + + /* number */ + if (argc <= 5) { + haltest_error("No number specified\n"); + return; + } + number = argv[5]; + + /* call mpty type */ + if (argc <= 6) { + haltest_error("No address type specified\n"); + return; + } + type = str2bthf_call_addrtype_t(argv[6]); + + EXEC(if_hf->phone_state_change, num_active, num_held, call_setup_state, + number, type); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hf); + + EXECV(if_hf->cleanup); + if_hf = NULL; +} + +static struct method methods[] = { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + STD_METHODH(init, "[<max_hf_clients>]"), + STD_METHODH(start_voice_recognition, "<addr>"), + STD_METHODH(stop_voice_recognition, "<addr>"), + STD_METHODCH(volume_control, "<vol_type> <volume> <addr>"), + STD_METHODH(cops_response, "<cops string> <addr>"), + STD_METHODCH(cind_response, + "<svc> <num_active> <num_held> <setup_state> <signal> " + "<roam> <batt_chg> <addr>"), + STD_METHODH(formatted_at_response, "<at_response> <addr>"), + STD_METHODCH(at_response, "<response_code> [<error_code> <bdaddr>]"), + STD_METHODCH(clcc_response, + "<index> <direction> <state> <mode> <mpty> <number> " + "<type> <addr>"), + STD_METHODCH(configure_wbs, "<addr> <wbs config>"), +#else + STD_METHOD(init), + STD_METHOD(start_voice_recognition), + STD_METHOD(stop_voice_recognition), + STD_METHODCH(volume_control, "<vol_type> <volume>"), + STD_METHODH(cops_response, "<cops string>"), + STD_METHODCH(cind_response, + "<svc> <num_active> <num_held> <setup_state> <signal> " + "<roam> <batt_chg>"), + STD_METHODH(formatted_at_response, "<at_response>"), + STD_METHODCH(at_response, "<response_code> [<error_code>]"), + STD_METHODCH(clcc_response, + "<index> <direction> <state> <mode> <mpty> <number> " + "<type>"), +#endif + STD_METHODCH(connect, "<addr>"), + STD_METHODCH(disconnect, "<addr>"), + STD_METHODCH(connect_audio, "<addr>"), + STD_METHODCH(disconnect_audio, "<addr>"), + STD_METHODCH(device_status_notification, + "<ntk_state> <svt_type> <signal> <batt_chg>"), + STD_METHODCH(phone_state_change, + "<num_active> <num_held> <setup_state> <number> " + "<type>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface hf_if = { + .name = "handsfree", + .methods = methods +};
diff --git a/android/client/if-hh.c b/android/client/if-hh.c new file mode 100644 index 0000000..25519e5 --- /dev/null +++ b/android/client/if-hh.c
@@ -0,0 +1,454 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include <hardware/bluetooth.h> +#include <hardware/bt_hh.h> + +#include "if-main.h" +#include "pollhandler.h" +#include "../hal-utils.h" + +const bthh_interface_t *if_hh = NULL; + +SINTMAP(bthh_protocol_mode_t, -1, "(unknown)") + DELEMENT(BTHH_REPORT_MODE), + DELEMENT(BTHH_BOOT_MODE), + DELEMENT(BTHH_UNSUPPORTED_MODE), +ENDMAP + +SINTMAP(bthh_report_type_t, -1, "(unknown)") + DELEMENT(BTHH_INPUT_REPORT), + DELEMENT(BTHH_OUTPUT_REPORT), + DELEMENT(BTHH_FEATURE_REPORT), +ENDMAP + +SINTMAP(bthh_connection_state_t, -1, "(unknown)") + DELEMENT(BTHH_CONN_STATE_CONNECTED), + DELEMENT(BTHH_CONN_STATE_CONNECTING), + DELEMENT(BTHH_CONN_STATE_DISCONNECTED), + DELEMENT(BTHH_CONN_STATE_DISCONNECTING), + DELEMENT(BTHH_CONN_STATE_FAILED_MOUSE_FROM_HOST), + DELEMENT(BTHH_CONN_STATE_FAILED_KBD_FROM_HOST), + DELEMENT(BTHH_CONN_STATE_FAILED_TOO_MANY_DEVICES), + DELEMENT(BTHH_CONN_STATE_FAILED_NO_BTHID_DRIVER), + DELEMENT(BTHH_CONN_STATE_FAILED_GENERIC), + DELEMENT(BTHH_CONN_STATE_UNKNOWN), +ENDMAP + +SINTMAP(bthh_status_t, -1, "(unknown)") + DELEMENT(BTHH_OK), + DELEMENT(BTHH_HS_HID_NOT_READY), + DELEMENT(BTHH_HS_INVALID_RPT_ID), + DELEMENT(BTHH_HS_TRANS_NOT_SPT), + DELEMENT(BTHH_HS_INVALID_PARAM), + DELEMENT(BTHH_HS_ERROR), + DELEMENT(BTHH_ERR), + DELEMENT(BTHH_ERR_SDP), + DELEMENT(BTHH_ERR_PROTO), + DELEMENT(BTHH_ERR_DB_FULL), + DELEMENT(BTHH_ERR_TOD_UNSPT), + DELEMENT(BTHH_ERR_NO_RES), + DELEMENT(BTHH_ERR_AUTH_FAILED), + DELEMENT(BTHH_ERR_HDL), +ENDMAP + +static char connected_device_addr[MAX_ADDR_STR_LEN]; +/* + * Callback for connection state change. + * state will have one of the values from bthh_connection_state_t + */ +static void connection_state_cb(bt_bdaddr_t *bd_addr, + bthh_connection_state_t state) +{ + char addr[MAX_ADDR_STR_LEN]; + + haltest_info("%s: bd_addr=%s connection_state=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, addr), + bthh_connection_state_t2str(state)); + if (state == BTHH_CONN_STATE_CONNECTED) + strcpy(connected_device_addr, addr); +} + +/* + * Callback for virtual unplug api. + * the status of the virtual unplug + */ +static void virtual_unplug_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status) +{ + char addr[MAX_ADDR_STR_LEN]; + + haltest_info("%s: bd_addr=%s hh_status=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, addr), + bthh_status_t2str(hh_status)); +} + +/* Callback for Android 5.0 handshake api. */ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void handshake_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status) +{ + char addr[MAX_ADDR_STR_LEN]; + + haltest_info("%s: bd_addr=%s hh_status=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, addr), + bthh_status_t2str(hh_status)); +} +#endif + +/* + * Callback for get hid info + * hid_info will contain attr_mask, sub_class, app_id, vendor_id, product_id, + * version, ctry_code, len + */ +static void hid_info_cb(bt_bdaddr_t *bd_addr, bthh_hid_info_t hid_info) +{ + char addr[MAX_ADDR_STR_LEN]; + + /* TODO: bluedroid does not seem to ever call this callback */ + haltest_info("%s: bd_addr=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, addr)); +} + +/* + * Callback for get/set protocol api. + * the protocol mode is one of the value from bthh_protocol_mode_t + */ +static void protocol_mode_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, + bthh_protocol_mode_t mode) +{ + char addr[MAX_ADDR_STR_LEN]; + + haltest_info("%s: bd_addr=%s hh_status=%s mode=%s\n", __func__, + bt_bdaddr_t2str(bd_addr, addr), + bthh_status_t2str(hh_status), + bthh_protocol_mode_t2str(mode)); +} + +/* Callback for get/set_idle_time api. */ +static void idle_time_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, + int idle_rate) +{ + char addr[MAX_ADDR_STR_LEN]; + + haltest_info("%s: bd_addr=%s hh_status=%s idle_rate=%d\n", __func__, + bt_bdaddr_t2str(bd_addr, addr), + bthh_status_t2str(hh_status), idle_rate); +} + + +/* + * Callback for get report api. + * if status is ok rpt_data contains the report data + */ +static void get_report_cb(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, + uint8_t *rpt_data, int rpt_size) +{ + char addr[MAX_ADDR_STR_LEN]; + + /* TODO: print actual report */ + haltest_info("%s: bd_addr=%s hh_status=%s rpt_size=%d\n", __func__, + bt_bdaddr_t2str(bd_addr, addr), + bthh_status_t2str(hh_status), rpt_size); +} + +static bthh_callbacks_t bthh_callbacks = { + .size = sizeof(bthh_callbacks), + .connection_state_cb = connection_state_cb, + .hid_info_cb = hid_info_cb, + .protocol_mode_cb = protocol_mode_cb, + .idle_time_cb = idle_time_cb, + .get_report_cb = get_report_cb, + .virtual_unplug_cb = virtual_unplug_cb, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .handshake_cb = handshake_cb +#endif +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hh); + + EXEC(if_hh->init, &bthh_callbacks); +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = (void *) connected_device_addr; + *enum_func = enum_one_string; + } +} + +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hh->connect, &addr); +} + +/* disconnect */ + +/* Same completion as connect_c */ +#define disconnect_c connect_c + +static void disconnect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hh->disconnect, &addr); +} + +/* virtual_unplug */ + +/* Same completion as connect_c */ +#define virtual_unplug_c connect_c + +static void virtual_unplug_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_hh->virtual_unplug, &addr); +} + +/* set_info */ + +/* Same completion as connect_c */ +#define set_info_c connect_c + +static void set_info_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bthh_hid_info_t hid_info; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + memset(&hid_info, 0, sizeof(hid_info)); + + /* + * This command is intentionally not supported. See comment from + * bt_hid_info() in android/hidhost.c + */ + EXEC(if_hh->set_info, &addr, hid_info); +} + +/* get_protocol */ + +static void get_protocol_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = connected_device_addr; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = TYPE_ENUM(bthh_protocol_mode_t); + *enum_func = enum_defines; + } +} + +static void get_protocol_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bthh_protocol_mode_t protocolMode; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + if (argc < 4) { + haltest_error("No protocol mode specified\n"); + return; + } + protocolMode = str2bthh_protocol_mode_t(argv[3]); + + EXEC(if_hh->get_protocol, &addr, protocolMode); +} + +/* set_protocol */ + +/* Same completion as get_protocol_c */ +#define set_protocol_c get_protocol_c + +static void set_protocol_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bthh_protocol_mode_t protocolMode; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + if (argc < 4) { + haltest_error("No protocol mode specified\n"); + return; + } + protocolMode = str2bthh_protocol_mode_t(argv[3]); + + EXEC(if_hh->set_protocol, &addr, protocolMode); +} + +/* get_report */ + +static void get_report_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = connected_device_addr; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = TYPE_ENUM(bthh_report_type_t); + *enum_func = enum_defines; + } +} + +static void get_report_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bthh_report_type_t reportType; + uint8_t reportId; + int bufferSize; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + if (argc < 4) { + haltest_error("No report type specified\n"); + return; + } + reportType = str2bthh_report_type_t(argv[3]); + + if (argc < 5) { + haltest_error("No reportId specified\n"); + return; + } + reportId = (uint8_t) atoi(argv[4]); + + if (argc < 6) { + haltest_error("No bufferSize specified\n"); + return; + } + bufferSize = atoi(argv[5]); + + EXEC(if_hh->get_report, &addr, reportType, reportId, bufferSize); +} + +/* set_report */ + +static void set_report_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = connected_device_addr; + *enum_func = enum_one_string; + } else if (argc == 4) { + *user = TYPE_ENUM(bthh_report_type_t); + *enum_func = enum_defines; + } +} + +static void set_report_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + bthh_report_type_t reportType; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + if (argc <= 3) { + haltest_error("No report type specified\n"); + return; + } + reportType = str2bthh_report_type_t(argv[3]); + + if (argc <= 4) { + haltest_error("No report specified\n"); + return; + } + + EXEC(if_hh->set_report, &addr, reportType, (char *) argv[4]); +} + +/* send_data */ + +static void send_data_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = connected_device_addr; + *enum_func = enum_one_string; + } +} + +static void send_data_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_hh); + VERIFY_ADDR_ARG(2, &addr); + + if (argc <= 3) { + haltest_error("No data to send specified\n"); + return; + } + + EXEC(if_hh->send_data, &addr, (char *) argv[3]); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hh); + + EXECV(if_hh->cleanup); +} + +/* Methods available in bthh_interface_t */ +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(connect, "<addr>"), + STD_METHODCH(disconnect, "<addr>"), + STD_METHODCH(virtual_unplug, "<addr>"), + STD_METHODCH(set_info, "<addr>"), + STD_METHODCH(get_protocol, "<addr> <mode>"), + STD_METHODCH(set_protocol, "<addr> <mode>"), + STD_METHODCH(get_report, "<addr> <type> <report_id> <size>"), + STD_METHODCH(set_report, "<addr> <type> <hex_encoded_report>"), + STD_METHODCH(send_data, "<addr> <hex_encoded_data>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface hh_if = { + .name = "hidhost", + .methods = methods +};
diff --git a/android/client/if-hl.c b/android/client/if-hl.c new file mode 100644 index 0000000..bd05671 --- /dev/null +++ b/android/client/if-hl.c
@@ -0,0 +1,377 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <string.h> + +#include <hardware/bluetooth.h> +#include <hardware/bt_hl.h> + +#include "if-main.h" +#include "pollhandler.h" +#include "../hal-utils.h" + +SINTMAP(bthl_mdep_role_t, -1, "(unknown)") + DELEMENT(BTHL_MDEP_ROLE_SOURCE), + DELEMENT(BTHL_MDEP_ROLE_SINK), +ENDMAP + +SINTMAP(bthl_channel_type_t, -1, "(unknown)") + DELEMENT(BTHL_CHANNEL_TYPE_RELIABLE), + DELEMENT(BTHL_CHANNEL_TYPE_STREAMING), + DELEMENT(BTHL_CHANNEL_TYPE_ANY), +ENDMAP + +SINTMAP(bthl_app_reg_state_t, -1, "(unknown)") + DELEMENT(BTHL_APP_REG_STATE_REG_SUCCESS), + DELEMENT(BTHL_APP_REG_STATE_REG_FAILED), + DELEMENT(BTHL_APP_REG_STATE_DEREG_SUCCESS), + DELEMENT(BTHL_APP_REG_STATE_DEREG_FAILED), +ENDMAP + +SINTMAP(bthl_channel_state_t, -1, "(unknown)") + DELEMENT(BTHL_CONN_STATE_CONNECTING), + DELEMENT(BTHL_CONN_STATE_CONNECTED), + DELEMENT(BTHL_CONN_STATE_DISCONNECTING), + DELEMENT(BTHL_CONN_STATE_DISCONNECTED), + DELEMENT(BTHL_CONN_STATE_DESTROYED), +ENDMAP + +#define APP_ID_SIZE 20 +#define MDEP_CFG_SIZE 10 +#define CHANNEL_ID_SIZE 50 + +struct channel_info { + int fd; +}; + +struct mdep_cfg { + uint8_t role; + struct channel_info channel[CHANNEL_ID_SIZE]; +}; + +struct { + struct mdep_cfg mdep[MDEP_CFG_SIZE]; +} app[APP_ID_SIZE]; + +const bthl_interface_t *if_hl = NULL; + +static void app_reg_state_cb(int app_id, bthl_app_reg_state_t state) +{ + haltest_info("%s: app_id=%d app_reg_state=%s\n", __func__, + app_id, bthl_app_reg_state_t2str(state)); +} + +static void channel_state_cb(int app_id, bt_bdaddr_t *bd_addr, + int index, int channel_id, + bthl_channel_state_t state, int fd) +{ + char addr[MAX_ADDR_STR_LEN]; + + haltest_info("%s: app_id=%d bd_addr=%s mdep_cfg_index=%d\n" + "channel_id=%d channel_state=%s fd=%d\n", __func__, + app_id, bt_bdaddr_t2str(bd_addr, addr), index, + channel_id, bthl_channel_state_t2str(state), fd); + + if (app_id >= APP_ID_SIZE || index >= MDEP_CFG_SIZE + || channel_id >= CHANNEL_ID_SIZE) { + haltest_error("exceeds maximum limit"); + return; + } + + if (state == BTHL_CONN_STATE_CONNECTED) { + app[app_id].mdep[index].channel[channel_id].fd = fd; + + /* + * PTS expects dummy data on fd when it + * connects in source role. + */ + if (app[app_id].mdep[index].role == BTHL_MDEP_ROLE_SOURCE) + if (write(fd, "0", sizeof("0")) < 0) + haltest_error("writing data on fd failed\n"); + + return; + } + + if (state == BTHL_CONN_STATE_DISCONNECTED || + state == BTHL_CONN_STATE_DESTROYED) { + if (app[app_id].mdep[index].channel[channel_id].fd >= 0) { + close(app[app_id].mdep[index].channel[channel_id].fd); + app[app_id].mdep[index].channel[channel_id].fd = -1; + } + } +} + +static bthl_callbacks_t hl_cbacks = { + .size = sizeof(hl_cbacks), + .app_reg_state_cb = app_reg_state_cb, + .channel_state_cb = channel_state_cb, +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + int i, j, k; + + for (i = 0; i < APP_ID_SIZE; i++) { + for (j = 0; j < MDEP_CFG_SIZE; j++) { + app[i].mdep[j].role = 0; + for (k = 0; k < CHANNEL_ID_SIZE; k++) + app[i].mdep[j].channel[k].fd = -1; + } + } + + + RETURN_IF_NULL(if_hl); + + EXEC(if_hl->init, &hl_cbacks); +} + +/* register_application */ + +static void register_application_p(int argc, const char **argv) +{ + bthl_reg_param_t reg; + uint16_t mdep_argc_init, mdep_argc_off; + int app_id = -1; + int i; + + RETURN_IF_NULL(if_hl); + + if (argc <= 2) { + haltest_error("No app name is specified\n"); + return; + } + + if (argc <= 3) { + haltest_error("No provider is specified\n"); + return; + } + + if (argc <= 4) { + haltest_error("No service name is specified\n"); + return; + } + + if (argc <= 5) { + haltest_error("No service description is specified\n"); + return; + } + + if (argc <= 6) { + haltest_error("No num of mdeps is specified\n"); + return; + } + + memset(®, 0, sizeof(reg)); + + if (argc != ((atoi(argv[6]) * 4) + 7)) { + haltest_error("mdep cfg argumetns are not proper\n"); + return; + } + + reg.application_name = argv[2]; + + if (strcmp("-", argv[3])) + reg.provider_name = argv[3]; + + if (strcmp("-", argv[4])) + reg.srv_name = argv[4]; + + if (strcmp("-", argv[5])) + reg.srv_desp = argv[5]; + + reg.number_of_mdeps = atoi(argv[6]); + + reg.mdep_cfg = malloc(reg.number_of_mdeps * sizeof(bthl_mdep_cfg_t)); + if (!reg.mdep_cfg) { + haltest_error("malloc failed\n"); + return; + } + mdep_argc_init = 7; + + for (i = 0; i < reg.number_of_mdeps; i++) { + mdep_argc_off = mdep_argc_init + (4 * i); + reg.mdep_cfg[i].mdep_role = + str2bthl_mdep_role_t(argv[mdep_argc_off]); + reg.mdep_cfg[i].data_type = atoi(argv[mdep_argc_off + 1]); + reg.mdep_cfg[i].channel_type = + str2bthl_channel_type_t(argv[mdep_argc_off + 2]); + + if (!strcmp("-", argv[mdep_argc_off + 3])) { + reg.mdep_cfg[i].mdep_description = NULL; + continue; + } + + reg.mdep_cfg[i].mdep_description = argv[mdep_argc_off + 3]; + } + + EXEC(if_hl->register_application, ®, &app_id); + + for (i = 0; i < reg.number_of_mdeps; i++) + app[app_id].mdep[i].role = reg.mdep_cfg[i].mdep_role; + + free(reg.mdep_cfg); +} + +/* unregister_application */ + +static void unregister_application_p(int argc, const char **argv) +{ + uint32_t app_id; + + RETURN_IF_NULL(if_hl); + + if (argc <= 2) { + haltest_error("No app id is specified"); + return; + } + + app_id = (uint32_t) atoi(argv[2]); + + EXEC(if_hl->unregister_application, app_id); +} + +/* connect_channel */ + +static void connect_channel_p(int argc, const char **argv) +{ + uint32_t app_id, mdep_cfg_index; + int channel_id = -1; + bt_bdaddr_t bd_addr; + + RETURN_IF_NULL(if_hl); + + if (argc <= 2) { + haltest_error("No app id is specified"); + return; + } + + VERIFY_ADDR_ARG(3, &bd_addr); + + if (argc <= 4) { + haltest_error("No mdep cfg index is specified"); + return; + } + + app_id = (uint32_t) atoi(argv[2]); + mdep_cfg_index = (uint32_t) atoi(argv[4]); + + EXEC(if_hl->connect_channel, app_id, &bd_addr, mdep_cfg_index, + &channel_id); +} + +/* destroy_channel */ + +static void destroy_channel_p(int argc, const char **argv) +{ + uint32_t channel_id; + + RETURN_IF_NULL(if_hl); + + if (argc <= 2) { + haltest_error("No channel id is specified"); + return; + } + + channel_id = (uint32_t) atoi(argv[2]); + + EXEC(if_hl->destroy_channel, channel_id); +} + +/* close_channel */ + +static void close_channel_p(int argc, const char **argv) +{ + uint32_t app_id; + uint8_t index; + int channel_id; + + RETURN_IF_NULL(if_hl); + + if (argc <= 2) { + haltest_error("No app id is specified"); + return; + } + + if (argc <= 3) { + haltest_error("No mdep_cfg_index is specified"); + return; + } + + if (argc <= 4) { + haltest_error("No channel_id is specified"); + return; + } + + app_id = (uint32_t) atoi(argv[2]); + if (app_id >= APP_ID_SIZE) { + haltest_error("Wrong app_id specified: %u\n", app_id); + return; + } + + index = (uint8_t) atoi(argv[3]); + if (index >= MDEP_CFG_SIZE) { + haltest_error("Wrong mdep cfg index: %u\n", index); + return; + } + + channel_id = atoi(argv[4]); + if (channel_id >= CHANNEL_ID_SIZE) { + haltest_error("Wrong channel id: %u\n", channel_id); + return; + } + + if (app[app_id].mdep[index].channel[channel_id].fd >= 0) { + shutdown(app[app_id].mdep[index].channel[channel_id].fd, + SHUT_RDWR); + app[app_id].mdep[index].channel[channel_id].fd = -1; + } +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_hl); + + EXECV(if_hl->cleanup); + if_hl = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODH(register_application, + "<app_name> <provider_name> <srv_name> <srv_descr>\n" + "<num_of_mdeps>\n" + "[[<mdep_role>] [<data_type>] [<channel_type>] [<mdep_descr>]]" + "..."), + STD_METHODH(unregister_application, "<app_id>"), + STD_METHODH(connect_channel, "<app_id> <bd_addr> <mdep_cfg_index>"), + STD_METHODH(destroy_channel, "<channel_id>"), + STD_METHODH(close_channel, "<app_id> <mdep_cfg_index> <channel_id>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface hl_if = { + .name = "hl", + .methods = methods +};
diff --git a/android/client/if-main.h b/android/client/if-main.h new file mode 100644 index 0000000..d239bb2 --- /dev/null +++ b/android/client/if-main.h
@@ -0,0 +1,198 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/un.h> +#include <poll.h> + +#include <hardware/audio.h> +#include <hardware/bluetooth.h> +#include <hardware/bt_av.h> +#include <hardware/bt_hh.h> +#include <hardware/bt_pan.h> +#include <hardware/bt_sock.h> +#include <hardware/bt_hf.h> +#include <hardware/bt_hl.h> + +#include "hal.h" + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +#include <hardware/bt_hf_client.h> +#include <hardware/bt_mce.h> +#endif + +#include <hardware/bt_rc.h> +#include <hardware/bt_gatt.h> +#include <hardware/bt_gatt_types.h> +#include <hardware/bt_gatt_client.h> +#include <hardware/bt_gatt_server.h> + +extern audio_hw_device_t *if_audio; + +/* Interfaces from hal that can be populated during application lifetime */ +extern const bt_interface_t *if_bluetooth; +extern const btav_interface_t *if_av; +extern const btrc_interface_t *if_rc; +extern const bthf_interface_t *if_hf; +extern const bthh_interface_t *if_hh; +extern const btpan_interface_t *if_pan; +extern const bthl_interface_t *if_hl; +extern const btsock_interface_t *if_sock; +extern const btgatt_interface_t *if_gatt; +extern const btgatt_server_interface_t *if_gatt_server; +extern const btgatt_client_interface_t *if_gatt_client; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +extern const btrc_ctrl_interface_t *if_rc_ctrl; +extern const bthf_client_interface_t *if_hf_client; +extern const btmce_interface_t *if_mce; +extern const btav_interface_t *if_av_sink; +#endif + +/* + * Structure defines top level interfaces that can be used in test tool + * this will contain values as: bluetooth, av, gatt, socket, pan... + */ +struct interface { + const char *name; /* interface name */ + struct method *methods; /* methods available for this interface */ +}; + +extern const struct interface audio_if; +extern const struct interface sco_if; +extern const struct interface bluetooth_if; +extern const struct interface av_if; +extern const struct interface rc_if; +extern const struct interface gatt_if; +extern const struct interface gatt_client_if; +extern const struct interface gatt_server_if; +extern const struct interface pan_if; +extern const struct interface sock_if; +extern const struct interface hf_if; +extern const struct interface hh_if; +extern const struct interface hl_if; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +extern const struct interface ctrl_rc_if; +extern const struct interface hf_client_if; +extern const struct interface mce_if; +extern const struct interface av_sink_if; +#endif + +/* Interfaces that will show up in tool (first part of command line) */ +extern const struct interface *interfaces[]; + +#define METHOD(name, func, comp, help) {name, func, comp, help} +#define STD_METHOD(m) {#m, m##_p, NULL, NULL} +#define STD_METHODC(m) {#m, m##_p, m##_c, NULL} +#define STD_METHODH(m, h) {#m, m##_p, NULL, h} +#define STD_METHODCH(m, h) {#m, m##_p, m##_c, h} +#define END_METHOD {"", NULL, NULL, NULL} + +/* + * Function to parse argument for function, argv[0] and argv[1] are already + * parsed before this function is called and contain interface and method name + * up to argc - 1 arguments are finished and should be used to decide which + * function enumeration function to return + */ +typedef void (*parse_and_call)(int argc, const char **argv); + +/* + * This is prototype of function that will return string for given number. + * Purpose is to enumerate string for auto completion. + * Function of this type will always be called in loop. + * First time function is called i = 0, then if function returns non-NULL + * it will be called again till for some value of i it will return NULL + */ +typedef const char *(*enum_func)(void *user, int i); + +/* + * This is prototype of function that when given argc, argv will + * fill enum_func with pointer to function that will enumerate + * parameters for argc argument, user will be passed to enum_func. + */ +typedef void (*tab_complete)(int argc, const char **argv, enum_func *enum_func, + void **user); + +/* + * For each method there is name and two functions to parse command line + * and call proper hal function on. + */ +struct method { + const char *name; + parse_and_call func; + tab_complete complete; + const char *help; +}; + +int haltest_error(const char *format, ...) + __attribute__((format(printf, 1, 2))); +int haltest_info(const char *format, ...)__attribute__((format(printf, 1, 2))); +int haltest_warn(const char *format, ...)__attribute__((format(printf, 1, 2))); + +/* Enumerator for discovered devices, to be used as tab completion enum_func */ +const char *enum_devices(void *v, int i); +const char *interface_name(void *v, int i); +const char *command_name(void *v, int i); +void add_remote_device(const bt_bdaddr_t *addr); +bool close_hw_bt_dev(void); + +const struct interface *get_interface(const char *name); +struct method *get_method(struct method *methods, const char *name); +struct method *get_command(const char *name); +const struct method *get_interface_method(const char *iname, + const char *mname); + +#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) + +/* Helper macro for executing function on interface and printing BT_STATUS */ +#define EXEC(f, ...) \ + { \ + if (f) { \ + int err = f(__VA_ARGS__); \ + haltest_info("%s: %s\n", #f, bt_status_t2str(err)); \ + } else { \ + haltest_info("%s is NULL\n", #f); \ + } \ + } + +/* Helper macro for executing void function on interface */ +#define EXECV(f, ...) \ + { \ + (void) f(__VA_ARGS__); \ + haltest_info("%s: void\n", #f); \ + } + +#define RETURN_IF_NULL(x) \ + do { if (!x) { haltest_error("%s is NULL\n", #x); return; } } while (0) + +#define VERIFY_ADDR_ARG(n, adr) \ + do { \ + if (n < argc) {\ + str2bt_bdaddr_t(argv[n], adr); \ + } else { \ + haltest_error("No address specified\n");\ + return;\ + } \ + } while (0)
diff --git a/android/client/if-mce.c b/android/client/if-mce.c new file mode 100644 index 0000000..5f101e0 --- /dev/null +++ b/android/client/if-mce.c
@@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include "if-main.h" +#include "../hal-utils.h" + +const btmce_interface_t *if_mce = NULL; + +/* + * Callback for get_remote_mas_instances + */ +static void btmce_remote_mas_instances_cb(bt_status_t status, + bt_bdaddr_t *bd_addr, + int num_instances, + btmce_mas_instance_t *instances) +{ + int i; + + haltest_info("%s: status=%s bd_addr=%s num_instance=%d\n", __func__, + bt_status_t2str(status), bdaddr2str(bd_addr), + num_instances); + + for (i = 0; i < num_instances; i++) + haltest_info("id=%d scn=%d msg_types=%d name=%s\n", + instances[i].id, instances[i].scn, + instances[i].msg_types, instances[i].p_name); +} + +static btmce_callbacks_t mce_cbacks = { + .size = sizeof(mce_cbacks), + .remote_mas_instances_cb = btmce_remote_mas_instances_cb, +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_mce); + + EXEC(if_mce->init, &mce_cbacks); +} + +static void get_remote_mas_instances_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +/* search for MAS instances on remote device */ + +static void get_remote_mas_instances_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_mce); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_mce->get_remote_mas_instances, &addr); +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(get_remote_mas_instances, "<addr>"), + END_METHOD +}; + +const struct interface mce_if = { + .name = "mce", + .methods = methods +};
diff --git a/android/client/if-pan.c b/android/client/if-pan.c new file mode 100644 index 0000000..b0c4b84 --- /dev/null +++ b/android/client/if-pan.c
@@ -0,0 +1,213 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include "if-main.h" +#include "../hal-utils.h" + +const btpan_interface_t *if_pan = NULL; + +typedef int btpan_role_t; + +SINTMAP(btpan_role_t, -1, "(unknown)") + DELEMENT(BTPAN_ROLE_NONE), + DELEMENT(BTPAN_ROLE_PANNAP), + DELEMENT(BTPAN_ROLE_PANU), +ENDMAP + +SINTMAP(btpan_connection_state_t, -1, "(unknown)") + DELEMENT(BTPAN_STATE_CONNECTED), + DELEMENT(BTPAN_STATE_CONNECTING), + DELEMENT(BTPAN_STATE_DISCONNECTED), + DELEMENT(BTPAN_STATE_DISCONNECTING), +ENDMAP + +SINTMAP(btpan_control_state_t, -1, "(unknown)") + DELEMENT(BTPAN_STATE_ENABLED), + DELEMENT(BTPAN_STATE_DISABLED), +ENDMAP + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void control_state_cb(btpan_control_state_t state, int local_role, + bt_status_t error, const char *ifname) +#else +static void control_state_cb(btpan_control_state_t state, bt_status_t error, + int local_role, const char *ifname) +#endif +{ + haltest_info("%s: state=%s error=%s local_role=%s ifname=%s\n", + __func__, btpan_control_state_t2str(state), + bt_status_t2str(error), btpan_role_t2str(local_role), + ifname); +} + +static char last_used_addr[MAX_ADDR_STR_LEN]; + +static void connection_state_cb(btpan_connection_state_t state, + bt_status_t error, const bt_bdaddr_t *bd_addr, + int local_role, int remote_role) +{ + haltest_info("%s: state=%s error=%s bd_addr=%s local_role=%s remote_role=%s\n", + __func__, btpan_connection_state_t2str(state), + bt_status_t2str(error), + bt_bdaddr_t2str(bd_addr, last_used_addr), + btpan_role_t2str(local_role), + btpan_role_t2str(remote_role)); +} + +static btpan_callbacks_t pan_cbacks = { + .size = sizeof(pan_cbacks), + .control_state_cb = control_state_cb, + .connection_state_cb = connection_state_cb +}; + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_pan); + + EXEC(if_pan->init, &pan_cbacks); +} + +/* enable */ + +static void enable_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(btpan_role_t); + *enum_func = enum_defines; + } +} + +static void enable_p(int argc, const char **argv) +{ + int local_role; + + RETURN_IF_NULL(if_pan); + + /* local role */ + if (argc < 3) { + haltest_error("No local mode specified\n"); + return; + } + local_role = str2btpan_role_t(argv[2]); + if (local_role == -1) + local_role = atoi(argv[2]); + + EXEC(if_pan->enable, local_role); +} + +/* get_local_role */ + +static void get_local_role_p(int argc, const char **argv) +{ + int local_role; + + RETURN_IF_NULL(if_pan); + + local_role = if_pan->get_local_role(); + haltest_info("local_role: %s\n", btpan_role_t2str(local_role)); +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } else if (argc == 4 || argc == 5) { + *user = TYPE_ENUM(btpan_role_t); + *enum_func = enum_defines; + } +} + +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + int local_role; + int remote_role; + + RETURN_IF_NULL(if_pan); + VERIFY_ADDR_ARG(2, &addr); + + /* local role */ + if (argc < 4) { + haltest_error("No local mode specified\n"); + return; + } + local_role = str2btpan_role_t(argv[3]); + if (local_role == -1) + local_role = atoi(argv[3]); + + /* remote role */ + if (argc < 5) { + haltest_error("No remote mode specified\n"); + return; + } + remote_role = str2btpan_role_t(argv[4]); + if (remote_role == -1) + remote_role = atoi(argv[4]); + + EXEC(if_pan->connect, &addr, local_role, remote_role); +} + +/* disconnect */ + +static void disconnect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = last_used_addr; + *enum_func = enum_one_string; + } +} + +static void disconnect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + + RETURN_IF_NULL(if_pan); + VERIFY_ADDR_ARG(2, &addr); + + EXEC(if_pan->disconnect, &addr); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_pan); + + EXECV(if_pan->cleanup); + if_pan = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(connect, "<addr> <local_role> <remote_role>"), + STD_METHODCH(enable, "<local_role>"), + STD_METHOD(get_local_role), + STD_METHODCH(disconnect, "<addr>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface pan_if = { + .name = "pan", + .methods = methods +};
diff --git a/android/client/if-rc-ctrl.c b/android/client/if-rc-ctrl.c new file mode 100644 index 0000000..3980764 --- /dev/null +++ b/android/client/if-rc-ctrl.c
@@ -0,0 +1,114 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include <hardware/bluetooth.h> +#include <hardware/bt_rc.h> + +#include "if-main.h" +#include "pollhandler.h" +#include "../hal-utils.h" + +const btrc_ctrl_interface_t *if_rc_ctrl = NULL; +static char last_addr[MAX_ADDR_STR_LEN]; + +static void passthrough_rsp_cb(int id, int key_state) +{ + haltest_info("%s: id=%d key_state=%d\n", __func__, id, key_state); +} + +static void connection_state_cb(bool state, bt_bdaddr_t *bd_addr) +{ + haltest_info("%s: state=%s bd_addr=%s\n", __func__, + state ? "true" : "false", + bt_bdaddr_t2str(bd_addr, last_addr)); +} + +static btrc_ctrl_callbacks_t rc_ctrl_cbacks = { + .size = sizeof(rc_ctrl_cbacks), + .passthrough_rsp_cb = passthrough_rsp_cb, + .connection_state_cb = connection_state_cb, +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_rc_ctrl); + + EXEC(if_rc_ctrl->init, &rc_ctrl_cbacks); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_rc_ctrl); + + EXECV(if_rc_ctrl->cleanup); + if_rc_ctrl = NULL; +} + +/* send_pass_through_cmd */ +static void send_pass_through_cmd_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = NULL; + *enum_func = enum_devices; + } +} + +static void send_pass_through_cmd_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + uint8_t key_code, key_state; + + RETURN_IF_NULL(if_rc); + VERIFY_ADDR_ARG(2, &addr); + + if (argc < 4) { + haltest_error("No key code specified\n"); + return; + } + + key_code = (uint8_t) atoi(argv[3]); + + if (argc < 5) { + haltest_error("No key state specified\n"); + return; + } + + key_state = (uint8_t) atoi(argv[4]); + + EXEC(if_rc_ctrl->send_pass_through_cmd, &addr, key_code, key_state); +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(send_pass_through_cmd, "<bd_addr> <key_code> <key_state>"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface ctrl_rc_if = { + .name = "rc-ctrl", + .methods = methods +};
diff --git a/android/client/if-rc.c b/android/client/if-rc.c new file mode 100644 index 0000000..ed65600 --- /dev/null +++ b/android/client/if-rc.c
@@ -0,0 +1,400 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include <hardware/bluetooth.h> +#include <hardware/bt_hh.h> + +#include "if-main.h" +#include "pollhandler.h" +#include "../hal-utils.h" + +const btrc_interface_t *if_rc = NULL; + +SINTMAP(btrc_play_status_t, -1, "(unknown)") + DELEMENT(BTRC_PLAYSTATE_STOPPED), + DELEMENT(BTRC_PLAYSTATE_PLAYING), + DELEMENT(BTRC_PLAYSTATE_PAUSED), + DELEMENT(BTRC_PLAYSTATE_FWD_SEEK), + DELEMENT(BTRC_PLAYSTATE_REV_SEEK), + DELEMENT(BTRC_PLAYSTATE_ERROR), +ENDMAP + +SINTMAP(btrc_media_attr_t, -1, "(unknown)") + DELEMENT(BTRC_MEDIA_ATTR_TITLE), + DELEMENT(BTRC_MEDIA_ATTR_ARTIST), + DELEMENT(BTRC_MEDIA_ATTR_ALBUM), + DELEMENT(BTRC_MEDIA_ATTR_TRACK_NUM), + DELEMENT(BTRC_MEDIA_ATTR_NUM_TRACKS), + DELEMENT(BTRC_MEDIA_ATTR_GENRE), + DELEMENT(BTRC_MEDIA_ATTR_PLAYING_TIME), +ENDMAP + +SINTMAP(btrc_status_t, -1, "(unknown)") + DELEMENT(BTRC_STS_BAD_CMD), + DELEMENT(BTRC_STS_BAD_PARAM), + DELEMENT(BTRC_STS_NOT_FOUND), + DELEMENT(BTRC_STS_INTERNAL_ERR), + DELEMENT(BTRC_STS_NO_ERROR), +ENDMAP + +SINTMAP(btrc_event_id_t, -1, "(unknown)") + DELEMENT(BTRC_EVT_PLAY_STATUS_CHANGED), + DELEMENT(BTRC_EVT_TRACK_CHANGE), + DELEMENT(BTRC_EVT_TRACK_REACHED_END), + DELEMENT(BTRC_EVT_TRACK_REACHED_START), + DELEMENT(BTRC_EVT_PLAY_POS_CHANGED), + DELEMENT(BTRC_EVT_APP_SETTINGS_CHANGED), +ENDMAP + +SINTMAP(btrc_notification_type_t, -1, "(unknown)") + DELEMENT(BTRC_NOTIFICATION_TYPE_INTERIM), + DELEMENT(BTRC_NOTIFICATION_TYPE_CHANGED), +ENDMAP + +static char last_addr[MAX_ADDR_STR_LEN]; + +static void remote_features_cb(bt_bdaddr_t *bd_addr, + btrc_remote_features_t features) +{ + haltest_info("%s: remote_bd_addr=%s features=%u\n", __func__, + bt_bdaddr_t2str(bd_addr, last_addr), features); +} + +static void get_play_status_cb(void) +{ + haltest_info("%s\n", __func__); +} + +static void list_player_app_attr_cb(void) +{ + haltest_info("%s\n", __func__); +} + +static void list_player_app_values_cb(btrc_player_attr_t attr_id) +{ + haltest_info("%s, attr_id=%d\n", __func__, attr_id); +} + +static void get_player_app_value_cb(uint8_t num_attr, + btrc_player_attr_t *p_attrs) +{ + int i; + + haltest_info("%s, num_attr=%d\n", __func__, num_attr); + + for (i = 0; i < num_attr; i++) + haltest_info("attribute=%u\n", p_attrs[i]); +} + +static void get_player_app_attrs_text_cb(uint8_t num_attr, + btrc_player_attr_t *p_attrs) +{ + int i; + + haltest_info("%s, num_attr=%d\n", __func__, num_attr); + + for (i = 0; i < num_attr; i++) + haltest_info("attribute=%u\n", p_attrs[i]); + +} + +static void get_player_app_values_text_cb(uint8_t attr_id, uint8_t num_val, + uint8_t *p_vals) +{ + haltest_info("%s, attr_id=%d num_val=%d values=%p\n", __func__, + attr_id, num_val, p_vals); +} + +static void set_player_app_value_cb(btrc_player_settings_t *p_vals) +{ + int i; + + haltest_info("%s, num_attr=%u\n", __func__, p_vals->num_attr); + + for (i = 0; i < p_vals->num_attr; i++) + haltest_info("attr id=%u, values=%u\n", p_vals->attr_ids[i], + p_vals->attr_values[i]); +} + +static void get_element_attr_cb(uint8_t num_attr, btrc_media_attr_t *attrs) +{ + uint8_t i; + + haltest_info("%s, num_of_attributes=%d\n", __func__, num_attr); + + for (i = 0; i < num_attr; i++) + haltest_info("attr id=%s\n", btrc_media_attr_t2str(attrs[i])); +} + +static void register_notification_cb(btrc_event_id_t event_id, uint32_t param) +{ + haltest_info("%s, event=%u param=%u\n", __func__, event_id, param); +} + +static void volume_change_cb(uint8_t volume, uint8_t ctype) +{ + haltest_info("%s, volume=%d ctype=%d\n", __func__, volume, ctype); +} + +static void passthrough_cmd_cb(int id, int key_state) +{ + haltest_info("%s, id=%d key_state=%d\n", __func__, id, key_state); +} + +static btrc_callbacks_t rc_cbacks = { + .size = sizeof(rc_cbacks), + .remote_features_cb = remote_features_cb, + .get_play_status_cb = get_play_status_cb, + .list_player_app_attr_cb = list_player_app_attr_cb, + .list_player_app_values_cb = list_player_app_values_cb, + .get_player_app_value_cb = get_player_app_value_cb, + .get_player_app_attrs_text_cb = get_player_app_attrs_text_cb, + .get_player_app_values_text_cb = get_player_app_values_text_cb, + .set_player_app_value_cb = set_player_app_value_cb, + .get_element_attr_cb = get_element_attr_cb, + .register_notification_cb = register_notification_cb, + .volume_change_cb = volume_change_cb, + .passthrough_cmd_cb = passthrough_cmd_cb, +}; + +/* init */ + +static void init_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_rc); + + EXEC(if_rc->init, &rc_cbacks); +} + +/* get_play_status_rsp */ + +static void get_play_status_rsp_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(btrc_play_status_t); + *enum_func = enum_defines; + } +} + +static void get_play_status_rsp_p(int argc, const char **argv) +{ + btrc_play_status_t play_status; + uint32_t song_len, song_pos; + + RETURN_IF_NULL(if_rc); + + if (argc <= 2) { + haltest_error("No play status specified"); + return; + } + + if (argc <= 3) { + haltest_error("No song length specified"); + return; + } + + if (argc <= 4) { + haltest_error("No song position specified"); + return; + } + + play_status = str2btrc_play_status_t(argv[2]); + song_len = (uint32_t) atoi(argv[3]); + song_pos = (uint32_t) atoi(argv[4]); + + EXEC(if_rc->get_play_status_rsp, play_status, song_len, song_pos); +} + +/* get_element_attr_rsp */ + +static void get_element_attr_rsp_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 4) { + *user = TYPE_ENUM(btrc_media_attr_t); + *enum_func = enum_defines; + } +} + +static void get_element_attr_rsp_p(int argc, const char **argv) +{ + uint8_t num_attr; + btrc_element_attr_val_t attrs; + + RETURN_IF_NULL(if_rc); + + if (argc <= 2) { + haltest_error("No number of attributes specified"); + return; + } + + if (argc <= 4) { + haltest_error("No attr id and value specified"); + return; + } + + num_attr = (uint8_t) atoi(argv[2]); + attrs.attr_id = str2btrc_media_attr_t(argv[3]); + strcpy((char *)attrs.text, argv[4]); + + EXEC(if_rc->get_element_attr_rsp, num_attr, &attrs); +} + +/* set_volume */ + +static void set_volume_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ +} + +static void set_volume_p(int argc, const char **argv) +{ + uint8_t volume; + + RETURN_IF_NULL(if_rc); + + if (argc <= 2) { + haltest_error("No volume specified"); + return; + } + + volume = (uint8_t) atoi(argv[2]); + + EXEC(if_rc->set_volume, volume); +} + +/* set_player_app_value_rsp */ + +static void set_player_app_value_rsp_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(btrc_status_t); + *enum_func = enum_defines; + } +} + +static void set_player_app_value_rsp_p(int argc, const char **argv) +{ + btrc_status_t rsp_status; + + RETURN_IF_NULL(if_rc); + + if (argc <= 2) { + haltest_error("No response status specified"); + return; + } + + rsp_status = str2btrc_status_t(argv[2]); + + EXEC(if_rc->set_player_app_value_rsp, rsp_status); +} + +/* register_notification_rsp */ + +static void register_notification_rsp_c(int argc, const char **argv, + enum_func *enum_func, void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(btrc_event_id_t); + *enum_func = enum_defines; + } + + if (argc == 4) { + *user = TYPE_ENUM(btrc_notification_type_t); + *enum_func = enum_defines; + } +} + +static void register_notification_rsp_p(int argc, const char **argv) +{ + btrc_event_id_t event_id; + btrc_notification_type_t type; + btrc_register_notification_t reg; + uint32_t song_pos; + uint64_t track; + + RETURN_IF_NULL(if_rc); + + memset(®, 0, sizeof(reg)); + event_id = str2btrc_event_id_t(argv[2]); + type = str2btrc_notification_type_t(argv[3]); + + switch (event_id) { + case BTRC_EVT_PLAY_STATUS_CHANGED: + reg.play_status = str2btrc_play_status_t(argv[4]); + break; + + case BTRC_EVT_TRACK_CHANGE: + track = strtoull(argv[5], NULL, 10); + memcpy(reg.track, &track, sizeof(btrc_uid_t)); + break; + + case BTRC_EVT_TRACK_REACHED_END: + case BTRC_EVT_TRACK_REACHED_START: + break; + + case BTRC_EVT_PLAY_POS_CHANGED: + song_pos = strtoul(argv[4], NULL, 10); + memcpy(®.song_pos, &song_pos, sizeof(uint32_t)); + break; + + case BTRC_EVT_APP_SETTINGS_CHANGED: + haltest_error("not supported"); + return; + } + + EXEC(if_rc->register_notification_rsp, event_id, type, ®); +} + +/* cleanup */ + +static void cleanup_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_rc); + + EXECV(if_rc->cleanup); + if_rc = NULL; +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHODCH(get_play_status_rsp, + "<play_status> <song_len> <song_pos>"), + STD_METHODCH(get_element_attr_rsp, "<num_attr> <attrs_id> <value>"), + STD_METHODCH(set_player_app_value_rsp, "<rsp_status>"), + STD_METHODCH(set_volume, "<volume>"), + STD_METHODCH(register_notification_rsp, + "<event_id> <type> <respective_data...>\n" + "BTRC_EVT_PLAY_STATUS_CHANGED <type> <play_status>\n" + "BTRC_EVT_TRACK_CHANGE <type> <track>\n" + "BTRC_EVT_TRACK_REACHED_END <type>\n" + "BTRC_EVT_TRACK_REACHED_START <type>\n" + "BTRC_EVT_PLAY_POS_CHANGED <type> <song_pos>\n"), + STD_METHOD(cleanup), + END_METHOD +}; + +const struct interface rc_if = { + .name = "rc", + .methods = methods +};
diff --git a/android/client/if-sco.c b/android/client/if-sco.c new file mode 100644 index 0000000..5a68ed5 --- /dev/null +++ b/android/client/if-sco.c
@@ -0,0 +1,815 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <pthread.h> +#include <unistd.h> +#include <math.h> + +#include "if-main.h" +#include "../hal-utils.h" + +audio_hw_device_t *if_audio_sco = NULL; +static struct audio_stream_out *stream_out = NULL; +static struct audio_stream_in *stream_in = NULL; + +static size_t buffer_size = 0; +static size_t buffer_size_in = 0; +static pthread_t play_thread = 0; +static pthread_mutex_t outstream_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +enum state { + STATE_STOPPED, + STATE_STOPPING, + STATE_PLAYING, + STATE_SUSPENDED, + STATE_MAX +}; + +SINTMAP(audio_channel_mask_t, -1, "(AUDIO_CHANNEL_INVALID)") + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_LOW_FREQUENCY), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_SIDE_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_SIDE_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_MONO), + DELEMENT(AUDIO_CHANNEL_OUT_STEREO), + DELEMENT(AUDIO_CHANNEL_OUT_QUAD), +#if ANDROID_VERSION < PLATFORM_VER(5, 0, 0) + DELEMENT(AUDIO_CHANNEL_OUT_SURROUND), +#else + DELEMENT(AUDIO_CHANNEL_OUT_QUAD_BACK), + DELEMENT(AUDIO_CHANNEL_OUT_QUAD_SIDE), + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1_BACK), + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1_SIDE), +#endif + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1), + DELEMENT(AUDIO_CHANNEL_OUT_7POINT1), + DELEMENT(AUDIO_CHANNEL_OUT_ALL), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), +ENDMAP + +SINTMAP(audio_format_t, -1, "(AUDIO_FORMAT_INVALID)") + DELEMENT(AUDIO_FORMAT_DEFAULT), + DELEMENT(AUDIO_FORMAT_PCM), + DELEMENT(AUDIO_FORMAT_MP3), + DELEMENT(AUDIO_FORMAT_AMR_NB), + DELEMENT(AUDIO_FORMAT_AMR_WB), + DELEMENT(AUDIO_FORMAT_AAC), + DELEMENT(AUDIO_FORMAT_HE_AAC_V1), + DELEMENT(AUDIO_FORMAT_HE_AAC_V2), + DELEMENT(AUDIO_FORMAT_VORBIS), + DELEMENT(AUDIO_FORMAT_MAIN_MASK), + DELEMENT(AUDIO_FORMAT_SUB_MASK), + DELEMENT(AUDIO_FORMAT_PCM_16_BIT), + DELEMENT(AUDIO_FORMAT_PCM_8_BIT), + DELEMENT(AUDIO_FORMAT_PCM_32_BIT), + DELEMENT(AUDIO_FORMAT_PCM_8_24_BIT), +ENDMAP + +static int current_state = STATE_STOPPED; + +#define SAMPLERATE 44100 +static short sample[SAMPLERATE]; +static uint16_t sample_pos; + +static void init_p(int argc, const char **argv) +{ + int err; + const hw_module_t *module; + audio_hw_device_t *device; + + err = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, "sco", &module); + if (err) { + haltest_error("hw_get_module_by_class returned %d\n", err); + return; + } + + err = audio_hw_device_open(module, &device); + if (err) { + haltest_error("audio_hw_device_open returned %d\n", err); + return; + } + + if_audio_sco = device; +} + +static int feed_from_file(short *buffer, void *data) +{ + FILE *in = data; + return fread(buffer, buffer_size, 1, in); +} + +static int feed_from_generator(short *buffer, void *data) +{ + size_t i = 0; + float volume = 0.5; + float *freq = data; + float f = 1; + + if (freq) + f = *freq; + + /* buffer_size is in bytes but we are using buffer of shorts (2 bytes)*/ + for (i = 0; i < buffer_size / sizeof(*buffer) - 1;) { + if (sample_pos >= SAMPLERATE) + sample_pos = sample_pos % SAMPLERATE; + + /* Use the same sample for both channels */ + buffer[i++] = sample[sample_pos] * volume; + buffer[i++] = sample[sample_pos] * volume; + + sample_pos += f; + } + + return buffer_size; +} + +static int feed_from_in(short *buffer, void *data) +{ + return stream_in->read(stream_in, buffer, buffer_size_in); +} + +static void prepare_sample(void) +{ + int x; + double s; + + haltest_info("Preparing audio sample...\n"); + + for (x = 0; x < SAMPLERATE; x++) { + /* prepare sinusoidal 1Hz sample */ + s = (2.0 * 3.14159) * ((double)x / SAMPLERATE); + s = sin(s); + + /* remap <-1, 1> to signed 16bit PCM range */ + sample[x] = s * 32767; + } + + sample_pos = 0; +} + +static void mono_to_stereo_pcm16(const int16_t *in, int16_t *out, size_t samples) +{ + size_t i; + + for (i = 0; i < samples; i++) { + out[2 * i] = in[i]; + out[2 * i + 1] = in[i]; + } +} + +static void *playback_thread(void *data) +{ + int (*filbuff_cb) (short*, void*); + short buffer[buffer_size / sizeof(short)]; + short buffer_in[buffer_size_in / sizeof(short)]; + size_t len = 0; + ssize_t w_len = 0; + FILE *in = data; + void *cb_data = NULL; + float freq = 440.0; + + /* Use file or fall back to generator */ + if (in) { + if (data == stream_in) + filbuff_cb = feed_from_in; + else { + filbuff_cb = feed_from_file; + cb_data = in; + } + } else { + prepare_sample(); + filbuff_cb = feed_from_generator; + cb_data = &freq; + } + + pthread_mutex_lock(&state_mutex); + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); + + do { + pthread_mutex_lock(&state_mutex); + + if (current_state == STATE_STOPPING) { + haltest_info("Detected stopping\n"); + pthread_mutex_unlock(&state_mutex); + break; + } else if (current_state == STATE_SUSPENDED) { + pthread_mutex_unlock(&state_mutex); + usleep(500); + continue; + } + + pthread_mutex_unlock(&state_mutex); + + if (data && data == stream_in) { + int chan_in = popcount(stream_in->common.get_channels(&stream_in->common)); + int chan_out = popcount(stream_out->common.get_channels(&stream_out->common)); + + len = filbuff_cb(buffer_in, cb_data); + + if (chan_in == 1 && chan_out == 2) { + mono_to_stereo_pcm16(buffer_in, + buffer, + buffer_size_in / 2); + } + } else + len = filbuff_cb(buffer, cb_data); + + pthread_mutex_lock(&outstream_mutex); + if (!stream_out) { + pthread_mutex_unlock(&outstream_mutex); + break; + } + + w_len = stream_out->write(stream_out, buffer, buffer_size); + pthread_mutex_unlock(&outstream_mutex); + } while (len && w_len > 0); + + if (in && data != stream_in) + fclose(in); + + pthread_mutex_lock(&state_mutex); + current_state = STATE_STOPPED; + pthread_mutex_unlock(&state_mutex); + + haltest_info("Done playing.\n"); + + return NULL; +} + +static void write_stereo_pcm16(const short *input, size_t len, FILE *out) +{ + short sample[2]; + size_t i; + + for (i = 0; i < len / 2; i++) { + sample[0] = input[i]; + sample[1] = input[i]; + + fwrite(sample, sizeof(sample), 1, out); + } +} + +static void *read_thread(void *data) +{ + int (*filbuff_cb) (short*, void*) = feed_from_in; + short buffer[buffer_size_in / sizeof(short)]; + ssize_t len = 0; + void *cb_data = NULL; + FILE *out = data; + + pthread_mutex_lock(&state_mutex); + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); + + do { + pthread_mutex_lock(&state_mutex); + + if (current_state == STATE_STOPPING) { + haltest_info("Detected stopping\n"); + pthread_mutex_unlock(&state_mutex); + break; + } else if (current_state == STATE_SUSPENDED) { + pthread_mutex_unlock(&state_mutex); + usleep(500); + continue; + } + + pthread_mutex_unlock(&state_mutex); + + len = filbuff_cb(buffer, cb_data); + if (len < 0) { + haltest_error("Error receiving SCO data"); + break; + } + + haltest_info("Read %zd bytes\n", len); + + if (out) { + write_stereo_pcm16(buffer, len, out); + haltest_info("Written %zd bytes\n", len * 2); + } + } while (len); + + if (out) + fclose(out); + + pthread_mutex_lock(&state_mutex); + current_state = STATE_STOPPED; + pthread_mutex_unlock(&state_mutex); + + haltest_info("Done reading.\n"); + + return NULL; +} + +static void play_p(int argc, const char **argv) +{ + const char *fname = NULL; + FILE *in = NULL; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_error("Invalid audio file path.\n"); + haltest_info("Using sound generator.\n"); + } else { + fname = argv[2]; + in = fopen(fname, "r"); + + if (in == NULL) { + haltest_error("Cannot open file: %s\n", fname); + return; + } + haltest_info("Playing file: %s\n", fname); + } + + if (buffer_size == 0) { + haltest_error("Invalid buffer size. Was stream_out opened?\n"); + goto fail; + } + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + haltest_error("Already playing or stream suspended!\n"); + pthread_mutex_unlock(&state_mutex); + goto fail; + } + pthread_mutex_unlock(&state_mutex); + + if (pthread_create(&play_thread, NULL, playback_thread, in) != 0) { + haltest_error("Cannot create playback thread!\n"); + goto fail; + } + + return; +fail: + if (in) + fclose(in); +} + +static void loop_p(int argc, const char **argv) +{ + int chan_out, chan_in; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + RETURN_IF_NULL(stream_in); + + chan_out = popcount(stream_out->common.get_channels(&stream_out->common)); + chan_in = popcount(stream_in->common.get_channels(&stream_in->common)); + + if (!buffer_size || !buffer_size_in) { + haltest_error("Invalid buffer sizes. Streams opened\n"); + return; + } + + if (buffer_size / chan_out != buffer_size_in / chan_in) { + haltest_error("read/write buffers differ, not supported\n"); + return; + } + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + haltest_error("Already playing or stream suspended!\n"); + pthread_mutex_unlock(&state_mutex); + return; + } + pthread_mutex_unlock(&state_mutex); + + if (pthread_create(&play_thread, NULL, playback_thread, + stream_in) != 0) + haltest_error("Cannot create playback thread!\n"); +} + +static void read_p(int argc, const char **argv) +{ + const char *fname = NULL; + FILE *out = NULL; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_in); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + haltest_error("Already playing or stream suspended!\n"); + pthread_mutex_unlock(&state_mutex); + return; + } + pthread_mutex_unlock(&state_mutex); + + if (argc < 3) { + haltest_error("Invalid audio file path.\n"); + haltest_info("Using read and through away\n"); + } else { + fname = argv[2]; + out = fopen(fname, "w"); + if (!out) { + haltest_error("Cannot open file: %s\n", fname); + return; + } + + haltest_info("Reading to file: %s\n", fname); + } + + if (!buffer_size_in) { + haltest_error("Invalid buffer size.\n"); + goto failed; + } + + if (pthread_create(&play_thread, NULL, read_thread, out) != 0) { + haltest_error("Cannot create playback thread!\n"); + goto failed; + } + + return; +failed: + if (out) + fclose(out); +} + +static void stop_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(play_thread); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_STOPPED || current_state == STATE_STOPPING) { + pthread_mutex_unlock(&state_mutex); + return; + } + + if (stream_out) { + pthread_mutex_lock(&outstream_mutex); + stream_out->common.standby(&stream_out->common); + pthread_mutex_unlock(&outstream_mutex); + } + + current_state = STATE_STOPPING; + pthread_mutex_unlock(&state_mutex); + + pthread_join(play_thread, NULL); + play_thread = 0; + + haltest_info("Ended %s\n", __func__); +} + +static void open_output_stream_p(int argc, const char **argv) +{ + struct audio_config *config; + int err; + + RETURN_IF_NULL(if_audio_sco); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_PLAYING) { + haltest_error("Already playing!\n"); + pthread_mutex_unlock(&state_mutex); + return; + } + pthread_mutex_unlock(&state_mutex); + + if (argc < 3) { + haltest_info("No sampling rate specified. Use default conf\n"); + config = NULL; + } else { + config = calloc(1, sizeof(struct audio_config)); + if (!config) + return; + + config->sample_rate = atoi(argv[2]); + config->channel_mask = AUDIO_CHANNEL_OUT_STEREO; + config->format = AUDIO_FORMAT_PCM_16_BIT; + } + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + err = if_audio_sco->open_output_stream(if_audio_sco, + 0, + AUDIO_DEVICE_OUT_ALL_SCO, + AUDIO_OUTPUT_FLAG_NONE, + config, + &stream_out, NULL); +#else + err = if_audio_sco->open_output_stream(if_audio_sco, + 0, + AUDIO_DEVICE_OUT_ALL_SCO, + AUDIO_OUTPUT_FLAG_NONE, + config, + &stream_out); +#endif + if (err < 0) { + haltest_error("open output stream returned %d\n", err); + goto failed; + } + + buffer_size = stream_out->common.get_buffer_size(&stream_out->common); + if (buffer_size == 0) + haltest_error("Invalid buffer size received!\n"); + else + haltest_info("Using buffer size: %zu\n", buffer_size); +failed: + if (config) + free(config); +} + +static void close_output_stream_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (play_thread) + stop_p(argc, argv); + + if_audio_sco->close_output_stream(if_audio_sco, stream_out); + + stream_out = NULL; + buffer_size = 0; +} + +static void open_input_stream_p(int argc, const char **argv) +{ + struct audio_config *config; + int err; + + RETURN_IF_NULL(if_audio_sco); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_PLAYING) { + haltest_error("Already playing!\n"); + pthread_mutex_unlock(&state_mutex); + return; + } + pthread_mutex_unlock(&state_mutex); + + if (argc < 3) { + haltest_info("No sampling rate specified. Use default conf\n"); + config = NULL; + } else { + config = calloc(1, sizeof(struct audio_config)); + if (!config) + return; + + config->sample_rate = atoi(argv[2]); + config->channel_mask = AUDIO_CHANNEL_OUT_MONO; + config->format = AUDIO_FORMAT_PCM_16_BIT; + } + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + err = if_audio_sco->open_input_stream(if_audio_sco, + 0, + AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, + config, + &stream_in, 0, NULL, 0); +#else + err = if_audio_sco->open_input_stream(if_audio_sco, + 0, + AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, + config, + &stream_in); +#endif + if (err < 0) { + haltest_error("open output stream returned %d\n", err); + goto failed; + } + + buffer_size_in = stream_in->common.get_buffer_size(&stream_in->common); + if (buffer_size_in == 0) + haltest_error("Invalid buffer size received!\n"); + else + haltest_info("Using buffer size: %zu\n", buffer_size_in); +failed: + if (config) + free(config); +} + +static void close_input_stream_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_in); + + if (play_thread) + stop_p(argc, argv); + + if_audio_sco->close_input_stream(if_audio_sco, stream_in); + + stream_in = NULL; + buffer_size_in = 0; +} + +static void cleanup_p(int argc, const char **argv) +{ + int err; + + RETURN_IF_NULL(if_audio_sco); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + pthread_mutex_unlock(&state_mutex); + close_output_stream_p(0, NULL); + } else { + pthread_mutex_unlock(&state_mutex); + } + + err = audio_hw_device_close(if_audio_sco); + if (err < 0) { + haltest_error("audio_hw_device_close returned %d\n", err); + return; + } + + if_audio_sco = NULL; +} + +static void suspend_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_PLAYING) { + pthread_mutex_unlock(&state_mutex); + return; + } + current_state = STATE_SUSPENDED; + pthread_mutex_unlock(&state_mutex); + + pthread_mutex_lock(&outstream_mutex); + stream_out->common.standby(&stream_out->common); + pthread_mutex_unlock(&outstream_mutex); +} + +static void resume_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_SUSPENDED) + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); +} + +static void get_latency_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + haltest_info("Output audio stream latency: %d\n", + stream_out->get_latency(stream_out)); +} + +static void get_buffer_size_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + haltest_info("Current output buffer size: %zu\n", + stream_out->common.get_buffer_size(&stream_out->common)); +} + +static void get_channels_p(int argc, const char **argv) +{ + audio_channel_mask_t channels; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + channels = stream_out->common.get_channels(&stream_out->common); + + haltest_info("Channels: %s\n", audio_channel_mask_t2str(channels)); +} + +static void get_format_p(int argc, const char **argv) +{ + audio_format_t format; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + format = stream_out->common.get_format(&stream_out->common); + + haltest_info("Format: %s\n", audio_format_t2str(format)); +} + +static void get_sample_rate_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + haltest_info("Current sample rate: %d\n", + stream_out->common.get_sample_rate(&stream_out->common)); +} + +static void get_parameters_p(int argc, const char **argv) +{ + const char *keystr; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_info("No keys given.\n"); + keystr = ""; + } else { + keystr = argv[2]; + } + + haltest_info("Current parameters: %s\n", + stream_out->common.get_parameters(&stream_out->common, + keystr)); +} + +static void set_parameters_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_error("No key=value; pairs given.\n"); + return; + } + + stream_out->common.set_parameters(&stream_out->common, argv[2]); +} + +static void set_sample_rate_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) + return; + + stream_out->common.set_sample_rate(&stream_out->common, atoi(argv[2])); +} + +static void init_check_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + + haltest_info("Init check result: %d\n", + if_audio_sco->init_check(if_audio_sco)); +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHOD(cleanup), + STD_METHODH(open_output_stream, "sample_rate"), + STD_METHOD(close_output_stream), + STD_METHODH(open_input_stream, "sampling rate"), + STD_METHOD(close_input_stream), + STD_METHODH(play, "<path to pcm file>"), + STD_METHOD(read), + STD_METHOD(loop), + STD_METHOD(stop), + STD_METHOD(suspend), + STD_METHOD(resume), + STD_METHOD(get_latency), + STD_METHOD(get_buffer_size), + STD_METHOD(get_channels), + STD_METHOD(get_format), + STD_METHOD(get_sample_rate), + STD_METHODH(get_parameters, "<closing>"), + STD_METHODH(set_parameters, "<closing=value>"), + STD_METHODH(set_sample_rate, "<sample rate>"), + STD_METHOD(init_check), + END_METHOD +}; + +const struct interface sco_if = { + .name = "sco", + .methods = methods +};
diff --git a/android/client/if-sock.c b/android/client/if-sock.c new file mode 100644 index 0000000..ee2c1e8 --- /dev/null +++ b/android/client/if-sock.c
@@ -0,0 +1,350 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <string.h> + +#include "if-main.h" +#include "pollhandler.h" +#include "../hal-utils.h" + +const btsock_interface_t *if_sock = NULL; + +SINTMAP(btsock_type_t, -1, "(unknown)") + DELEMENT(BTSOCK_RFCOMM), + DELEMENT(BTSOCK_SCO), + DELEMENT(BTSOCK_L2CAP), +ENDMAP + +#define MAX_LISTEN_FD 15 +static int listen_fd[MAX_LISTEN_FD]; +static int listen_fd_count; + +static const char * const uuids[] = { + "00001101", "00001105", "0000112f", NULL +}; + +/* + * This function reads data from file descriptor and + * prints it to the user + */ +static void receive_from_client(struct pollfd *pollfd) +{ + char buf[16]; + /* + * Buffer for lines: + * 41 42 43 20 20 00 31 32 00 07 04 00 00 00 00 00 ABC .12..... + */ + char outbuf[sizeof(buf) * 4 + 2]; + int i; + int ret; + + if (pollfd->revents & POLLHUP) { + haltest_error("Disconnected fd=%d\n", pollfd->fd); + poll_unregister_fd(pollfd->fd, receive_from_client); + } else if (pollfd->revents & POLLIN) { + haltest_info("receiving from client fd=%d\n", pollfd->fd); + + do { + memset(outbuf, ' ', sizeof(outbuf)); + outbuf[sizeof(outbuf) - 1] = 0; + ret = recv(pollfd->fd, buf, sizeof(buf), MSG_DONTWAIT); + + for (i = 0; i < ret; ++i) + sprintf(outbuf + i * 3, "%02X ", + (unsigned) buf[i]); + outbuf[i * 3] = ' '; + for (i = 0; i < ret; ++i) + sprintf(outbuf + 48 + i, "%c", + (isprint(buf[i]) ? buf[i] : '.')); + if (ret > 0) + haltest_info("%s\n", outbuf); + } while (ret > 0); + } else { + /* For now disconnect on all other events */ + haltest_error("Poll event %x\n", pollfd->revents); + poll_unregister_fd(pollfd->fd, receive_from_client); + } +} + +/* + * This function read from fd socket information about + * connected socket + */ +static void receive_sock_connect_signal(struct pollfd *pollfd) +{ + sock_connect_signal_t cs; + char addr_str[MAX_ADDR_STR_LEN]; + + if (pollfd->revents & POLLIN) { + int ret; + + poll_unregister_fd(pollfd->fd, receive_sock_connect_signal); + ret = read(pollfd->fd, &cs, sizeof(cs)); + if (ret != sizeof(cs)) { + haltest_info("Read on connect return %d\n", ret); + return; + } + + haltest_info("Connection to %s channel %d status=%d\n", + bt_bdaddr_t2str(&cs.bd_addr, addr_str), + cs.channel, cs.status); + + if (cs.status == 0) + poll_register_fd(pollfd->fd, POLLIN, + receive_from_client); + } + + if (pollfd->revents & POLLHUP) { + haltest_error("Disconnected fd=%d revents=0x%X\n", pollfd->fd, + pollfd->revents); + poll_unregister_fd(pollfd->fd, receive_sock_connect_signal); + } +} + +/* + * This function read from fd socket information about + * incoming connection and starts monitoring new connection + * on file descriptor read from fd. + */ +static void read_accepted(int fd) +{ + int ret; + struct msghdr msg; + struct iovec iv; + char cmsgbuf[CMSG_SPACE(1)]; + struct cmsghdr *cmsgptr; + sock_connect_signal_t cs; + int accepted_fd = -1; + char addr_str[MAX_ADDR_STR_LEN]; + + memset(&msg, 0, sizeof(msg)); + memset(&iv, 0, sizeof(iv)); + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + + iv.iov_base = &cs; + iv.iov_len = sizeof(cs); + + msg.msg_iov = &iv; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + do { + ret = recvmsg(fd, &msg, MSG_NOSIGNAL); + } while (ret < 0 && errno == EINTR); + + if (ret < 16 || + (msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) != 0) + haltest_error("Failed to accept connection\n"); + + for (cmsgptr = CMSG_FIRSTHDR(&msg); + cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + int count; + + if (cmsgptr->cmsg_level != SOL_SOCKET || + cmsgptr->cmsg_type != SCM_RIGHTS) + continue; + + memcpy(&accepted_fd, CMSG_DATA(cmsgptr), sizeof(accepted_fd)); + count = ((cmsgptr->cmsg_len - CMSG_LEN(0)) / sizeof(int)); + + if (count != 1) + haltest_error("Failed to accept descriptors count=%d\n", + count); + + break; + } + + haltest_info("Incoming connection from %s channel %d status=%d fd=%d\n", + bt_bdaddr_t2str(&cs.bd_addr, addr_str), + cs.channel, cs.status, accepted_fd); + poll_register_fd(accepted_fd, POLLIN, receive_from_client); +} + +/* handles incoming connections on socket */ +static void client_connected(struct pollfd *pollfd) +{ + haltest_info("client connected %x\n", pollfd->revents); + + if (pollfd->revents & POLLHUP) + poll_unregister_fd(pollfd->fd, client_connected); + else if (pollfd->revents & POLLIN) + read_accepted(pollfd->fd); +} + +/* listen */ + +static void listen_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *user = TYPE_ENUM(btsock_type_t); + *enum_func = enum_defines; + } else if (argc == 5) { + *user = (void *) uuids; + *enum_func = enum_strings; + } +} + +static void listen_p(int argc, const char **argv) +{ + btsock_type_t type; + const char *service_name; + bt_uuid_t service_uuid; + int channel; + int sock_fd = -1; + int flags; + + RETURN_IF_NULL(if_sock); + + /* Socket type */ + if (argc < 3) { + haltest_error("No socket type specified\n"); + return; + } + type = str2btsock_type_t(argv[2]); + if ((int) type == -1) + type = atoi(argv[2]); + + /* service name */ + if (argc < 4) { + haltest_error("No service name specified\n"); + return; + } + service_name = argv[3]; + + /* uuid */ + if (argc < 5) { + haltest_error("No uuid specified\n"); + return; + } + str2bt_uuid_t(argv[4], &service_uuid); + + /* channel */ + channel = argc > 5 ? atoi(argv[5]) : 0; + + /* flags */ + flags = argc > 6 ? atoi(argv[6]) : 0; + + if (listen_fd_count >= MAX_LISTEN_FD) { + haltest_error("Max (%d) listening sockets exceeded\n", + listen_fd_count); + return; + } + EXEC(if_sock->listen, type, service_name, + &service_uuid.uu[0], channel, &sock_fd, flags); + if (sock_fd > 0) { + int channel = 0; + int ret = read(sock_fd, &channel, 4); + if (ret != 4) + haltest_info("Read channel failed\n"); + haltest_info("Channel returned from first read %d\n", channel); + listen_fd[listen_fd_count++] = sock_fd; + poll_register_fd(sock_fd, POLLIN, client_connected); + } +} + +/* connect */ + +static void connect_c(int argc, const char **argv, enum_func *enum_func, + void **user) +{ + if (argc == 3) { + *enum_func = enum_devices; + } else if (argc == 4) { + *user = TYPE_ENUM(btsock_type_t); + *enum_func = enum_defines; + } else if (argc == 5) { + *user = (void *) uuids; + *enum_func = enum_strings; + } +} + +static void connect_p(int argc, const char **argv) +{ + bt_bdaddr_t addr; + btsock_type_t type; + bt_uuid_t uuid; + int channel; + int sock_fd = -1; + int flags; + + /* Address */ + if (argc <= 2) { + haltest_error("No address specified\n"); + return; + } + str2bt_bdaddr_t(argv[2], &addr); + + /* Socket type */ + if (argc <= 3) { + haltest_error("No socket type specified\n"); + return; + } + type = str2btsock_type_t(argv[3]); + if ((int) type == -1) + type = atoi(argv[3]); + + /* uuid */ + if (argc <= 4) { + haltest_error("No uuid specified\n"); + return; + } + str2bt_uuid_t(argv[4], &uuid); + + /* channel */ + if (argc <= 5) { + haltest_error("No channel specified\n"); + return; + } + channel = atoi(argv[5]); + + /* flags */ + flags = argc <= 6 ? 0 : atoi(argv[6]); + + RETURN_IF_NULL(if_sock); + + EXEC(if_sock->connect, &addr, type, &uuid.uu[0], channel, &sock_fd, + flags); + if (sock_fd > 0) { + int channel = 0; + int ret = read(sock_fd, &channel, 4); + + if (ret != 4) + haltest_info("Read channel failed\n"); + haltest_info("Channel returned from first read %d\n", channel); + listen_fd[listen_fd_count++] = sock_fd; + poll_register_fd(sock_fd, POLLIN, receive_sock_connect_signal); + } +} + +/* Methods available in btsock_interface_t */ +static struct method methods[] = { + STD_METHODCH(listen, + "<sock_type> <srvc_name> <uuid> [<channel>] [<flags>]"), + STD_METHODCH(connect, + "<addr> <sock_type> <uuid> <channel> [<flags>]"), + END_METHOD +}; + +const struct interface sock_if = { + .name = "socket", + .methods = methods +};
diff --git a/android/client/pollhandler.c b/android/client/pollhandler.c new file mode 100644 index 0000000..6160921 --- /dev/null +++ b/android/client/pollhandler.c
@@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <errno.h> +#include <poll.h> + +#include "pollhandler.h" + +/* + * Code that allows to poll multiply file descriptors for events + * File descriptors can be added and removed at runtime + * + * Call poll_register_fd function first to add file descriptors to monitor + * Then call poll_dispatch_loop that will poll all registered file descriptors + * as long as they are not unregistered. + * + * When event happen on given fd appropriate user supplied handler is called + */ + +/* Maximum number of files to monitor */ +#define MAX_OPEN_FD 10 + +/* Storage for pollfd structures for monitored file descriptors */ +static struct pollfd fds[MAX_OPEN_FD]; +static poll_handler fds_handler[MAX_OPEN_FD]; +/* Number of registered file descriptors */ +static int fds_count = 0; + +/* + * Function polls file descriptor in loop and calls appropriate handler + * on event. Function returns when there is no more file descriptor to + * monitor + */ +void poll_dispatch_loop(void) +{ + while (fds_count > 0) { + int i; + int cur_fds_count = fds_count; + int ready = poll(fds, fds_count, 1000); + + for (i = 0; i < fds_count && ready > 0; ++i) { + if (fds[i].revents == 0) + continue; + + fds_handler[i](fds + i); + ready--; + /* + * If handler was remove from table + * just skip the rest and poll again + * This is due to reordering of tables in + * register/unregister functions + */ + if (cur_fds_count != fds_count) + break; + } + } +} + +/* + * Registers file descriptor to be monitored for events (see man poll(2)) + * for events. + * + * return non negative value on success + * -EMFILE when there are to much descriptors + */ +int poll_register_fd(int fd, short events, poll_handler ph) +{ + if (fds_count >= MAX_OPEN_FD) + return -EMFILE; + + fds_handler[fds_count] = ph; + fds[fds_count].fd = fd; + fds[fds_count].events = events; + fds_count++; + + return fds_count; +} + +/* + * Unregisters file descriptor + * Both fd and ph must match previously registered data + * + * return 0 if unregister succeeded + * -EBADF if arguments do not match any register handler + */ +int poll_unregister_fd(int fd, poll_handler ph) +{ + int i; + + for (i = 0; i < fds_count; ++i) { + if (fds_handler[i] == ph && fds[i].fd == fd) { + fds_count--; + if (i < fds_count) { + fds[i].fd = fds[fds_count].fd; + fds[i].events = fds[fds_count].events; + fds_handler[i] = fds_handler[fds_count]; + } + return 0; + } + } + return -EBADF; +}
diff --git a/android/client/pollhandler.h b/android/client/pollhandler.h new file mode 100644 index 0000000..e2f22df --- /dev/null +++ b/android/client/pollhandler.h
@@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <poll.h> + +/* Function to be called when there are event for some descriptor */ +typedef void (*poll_handler)(struct pollfd *pollfd); + +int poll_register_fd(int fd, short events, poll_handler ph); +int poll_unregister_fd(int fd, poll_handler ph); + +void poll_dispatch_loop(void);
diff --git a/android/client/tabcompletion.c b/android/client/tabcompletion.c new file mode 100644 index 0000000..bcca5fa --- /dev/null +++ b/android/client/tabcompletion.c
@@ -0,0 +1,374 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include "if-main.h" +#include "terminal.h" + +/* how many times tab was hit */ +static int tab_hit_count; + +typedef struct split_arg { + struct split_arg *next; /* next argument in buffer */ + const char *origin; /* pointer to original argument */ + char ntcopy[1]; /* null terminated copy of argument */ +} split_arg_t; + +/* function returns method of given name or NULL if not found */ +const struct method *get_interface_method(const char *iname, + const char *mname) +{ + const struct interface *iface = get_interface(iname); + + if (iface == NULL) + return NULL; + + return get_method(iface->methods, mname); +} + +/* prints matching elements */ +static void print_matches(enum_func f, void *user, const char *prefix, int len) +{ + int i; + const char *enum_name; + + putchar('\n'); + for (i = 0; NULL != (enum_name = f(user, i)); ++i) { + if (strncmp(enum_name, prefix, len) == 0) + printf("%s\t", enum_name); + } + putchar('\n'); + terminal_draw_command_line(); +} + +/* + * This function splits command line into linked list of arguments. + * line_buffer - pointer to input command line + * size - size of command line to parse + * buf - output buffer to keep split arguments list + * buf_size_in_bytes - size of buf + */ +static int split_command(const char *line_buffer, int size, split_arg_t *buf, + int buf_size_in_bytes) +{ + split_arg_t *prev = NULL; + split_arg_t *arg = buf; + int argc = 0; + const char *p = line_buffer; + const char *e = p + (size > 0 ? size : (int) strlen(p)); + int len; + + do { + while (p < e && isspace(*p)) + p++; + arg->origin = p; + arg->next = NULL; + while (p < e && !isspace(*p)) + p++; + len = p - arg->origin; + if (&arg->ntcopy[0] + len + 1 > + (const char *) buf + buf_size_in_bytes) + break; + strncpy(arg->ntcopy, arg->origin, len); + arg->ntcopy[len] = 0; + if (prev != NULL) + prev->next = arg; + prev = arg; + arg += (2 * sizeof(*arg) + len) / sizeof(*arg); + argc++; + } while (p < e); + + return argc; +} + +/* Function to enumerate method names */ +static const char *methods_name(void *v, int i) +{ + const struct interface *iface = v; + + return iface->methods[i].name[0] ? iface->methods[i].name : NULL; +} + +struct command_completion_args; +typedef void (*short_help)(struct command_completion_args *args); + +struct command_completion_args { + const split_arg_t *arg; /* list of arguments */ + const char *typed; /* last typed element */ + enum_func func; /* enumerating function */ + void *user; /* argument to enumerating function */ + short_help help; /* help function */ + const char *user_help; /* additional data (used by short_help) */ +}; + +/* complete command line */ +static void tab_completion(struct command_completion_args *args) +{ + const char *name = args->typed; + const int len = strlen(name); + int i; + int j; + char prefix[128] = {0}; + int prefix_len = 0; + int count = 0; + const char *enum_name; + + for (i = 0; NULL != (enum_name = args->func(args->user, i)); ++i) { + /* prefix does not match */ + if (strncmp(enum_name, name, len) != 0) + continue; + + /* prefix matches first time */ + if (count++ == 0) { + strcpy(prefix, enum_name); + prefix_len = strlen(prefix); + continue; + } + + /* Prefix matches next time reduce prefix to common part */ + for (j = 0; prefix[j] != 0 + && prefix[j] == enum_name[j];) + ++j; + prefix_len = j; + prefix[j] = 0; + } + + if (count == 0) { + /* no matches */ + if (args->help != NULL) + args->help(args); + tab_hit_count = 0; + return; + } + + /* len == prefix_len => nothing new was added */ + if (len == prefix_len) { + if (count != 1) { + if (tab_hit_count == 1) { + putchar('\a'); + } else if (tab_hit_count == 2 || + args->help == NULL) { + print_matches(args->func, + args->user, name, len); + } else { + args->help(args); + tab_hit_count = 1; + } + } else if (count == 1) { + /* nothing to add, exact match add space */ + terminal_insert_into_command_line(" "); + } + } else { + /* new chars can be added from some interface name(s) */ + if (count == 1) { + /* exact match, add space */ + prefix[prefix_len++] = ' '; + prefix[prefix_len] = '\0'; + } + + terminal_insert_into_command_line(prefix + len); + tab_hit_count = 0; + } +} + +/* interface completion */ +static void command_completion(split_arg_t *arg) +{ + struct command_completion_args args = { + .arg = arg, + .typed = arg->ntcopy, + .func = command_name + }; + + tab_completion(&args); +} + +/* method completion */ +static void method_completion(const struct interface *iface, split_arg_t *arg) +{ + struct command_completion_args args = { + .arg = arg, + .typed = arg->next->ntcopy, + .func = methods_name, + .user = (void *) iface + }; + + if (iface == NULL) + return; + + tab_completion(&args); +} + +static const char *bold = "\x1b[1m"; +static const char *normal = "\x1b[0m"; + +static bool find_nth_argument(const char *str, int n, const char **s, + const char **e) +{ + const char *p = str; + int argc = 0; + *e = NULL; + + while (p != NULL && *p != 0) { + + while (isspace(*p)) + ++p; + + if (n == argc) + *s = p; + + if (*p == '[') { + p = strchr(p, ']'); + if (p != NULL) + *e = ++p; + } else if (*p == '<') { + p = strchr(p, '>'); + if (p != NULL) + *e = ++p; + } else { + *e = strchr(p, ' '); + if (*e == NULL) + *e = p + strlen(p); + p = *e; + } + + if (n == argc) + break; + + argc++; + *e = NULL; + } + return *e != NULL; +} + +/* prints short help on method for interface */ +static void method_help(struct command_completion_args *args) +{ + int argc; + const split_arg_t *arg = args->arg; + const char *sb = NULL; + const char *eb = NULL; + const char *arg1 = ""; + int arg1_size = 0; /* size of method field (for methods > 0) */ + + if (args->user_help == NULL) + return; + + for (argc = 0; arg != NULL; argc++) + arg = arg->next; + + /* Check if this is method from interface */ + if (get_command(args->arg->ntcopy) == NULL) { + /* if so help is missing interface and method name */ + arg1 = args->arg->next->ntcopy; + arg1_size = strlen(arg1) + 1; + } + + find_nth_argument(args->user_help, argc - (arg1_size ? 3 : 2), + &sb, &eb); + + if (eb != NULL) + haltest_info("%s %-*s%.*s%s%.*s%s%s\n", args->arg->ntcopy, + arg1_size, arg1, (int) (sb - args->user_help), + args->user_help, bold, (int) (eb - sb), + sb, normal, eb); + else + haltest_info("%s %-*s%s\n", args->arg->ntcopy, + arg1_size, arg1, args->user_help); +} + +/* So we have empty enumeration */ +static const char *return_null(void *user, int i) +{ + return NULL; +} + +/* + * parameter completion function + * argc - number of elements in arg list + * arg - list of arguments + * method - method to get completion from (can be NULL) + */ +static void param_completion(int argc, const split_arg_t *arg, + const struct method *method, int hlpix) +{ + int i; + const char *argv[argc]; + const split_arg_t *tmp = arg; + struct command_completion_args args = { + .arg = arg, + .func = return_null + }; + + /* prepare standard argv from arg */ + for (i = 0; i < argc; ++i) { + argv[i] = tmp->ntcopy; + tmp = tmp->next; + } + + if (method != NULL && method->complete != NULL) { + /* ask method for completion function */ + method->complete(argc, argv, &args.func, &args.user); + } + + /* If method provided enumeration function call try to complete */ + if (args.func != NULL) { + args.typed = argv[argc - 1]; + args.help = method_help; + args.user_help = method ? method->help : NULL; + + tab_completion(&args); + } +} + +/* + * This method gets called when user tapped tab key. + * line - points to command line + * len - size of line that should be used for completions. This should be + * cursor position during tab hit. + */ +void process_tab(const char *line, int len) +{ + int argc; + static split_arg_t buf[(LINE_BUF_MAX * 2) / sizeof(split_arg_t)]; + const struct method *method; + + argc = split_command(line, len, buf, sizeof(buf)); + tab_hit_count++; + + if (argc == 0) + return; + + if (argc == 1) { + command_completion(buf); + return; + } + + method = get_command(buf[0].ntcopy); + if (method != NULL) { + param_completion(argc, buf, method, 1); + } else if (argc == 2) { + method_completion(get_interface(buf[0].ntcopy), buf); + } else { + /* Find method for <interface, name> pair */ + method = get_interface_method(buf[0].ntcopy, + buf[0].next->ntcopy); + param_completion(argc, buf, method, 2); + } +}
diff --git a/android/client/terminal.c b/android/client/terminal.c new file mode 100644 index 0000000..f7b56de --- /dev/null +++ b/android/client/terminal.c
@@ -0,0 +1,824 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdbool.h> +#include <termios.h> +#include <stdlib.h> + +#include "terminal.h" +#include "history.h" + +/* + * Character sequences recognized by code in this file + * Leading ESC 0x1B is not included + */ +#define SEQ_INSERT "[2~" +#define SEQ_DELETE "[3~" +#define SEQ_HOME "OH" +#define SEQ_END "OF" +#define SEQ_PGUP "[5~" +#define SEQ_PGDOWN "[6~" +#define SEQ_LEFT "[D" +#define SEQ_RIGHT "[C" +#define SEQ_UP "[A" +#define SEQ_DOWN "[B" +#define SEQ_STAB "[Z" +#define SEQ_M_n "n" +#define SEQ_M_p "p" +#define SEQ_CLEFT "[1;5D" +#define SEQ_CRIGHT "[1;5C" +#define SEQ_CUP "[1;5A" +#define SEQ_CDOWN "[1;5B" +#define SEQ_SLEFT "[1;2D" +#define SEQ_SRIGHT "[1;2C" +#define SEQ_SUP "[1;2A" +#define SEQ_SDOWN "[1;2B" +#define SEQ_MLEFT "[1;3D" +#define SEQ_MRIGHT "[1;3C" +#define SEQ_MUP "[1;3A" +#define SEQ_MDOWN "[1;3B" + +#define KEY_SEQUENCE(k) { KEY_##k, SEQ_##k } +struct ansii_sequence { + int code; + const char *sequence; +}; + +/* Table connects single int key codes with character sequences */ +static const struct ansii_sequence ansii_sequnces[] = { + KEY_SEQUENCE(INSERT), + KEY_SEQUENCE(DELETE), + KEY_SEQUENCE(HOME), + KEY_SEQUENCE(END), + KEY_SEQUENCE(PGUP), + KEY_SEQUENCE(PGDOWN), + KEY_SEQUENCE(LEFT), + KEY_SEQUENCE(RIGHT), + KEY_SEQUENCE(UP), + KEY_SEQUENCE(DOWN), + KEY_SEQUENCE(CLEFT), + KEY_SEQUENCE(CRIGHT), + KEY_SEQUENCE(CUP), + KEY_SEQUENCE(CDOWN), + KEY_SEQUENCE(SLEFT), + KEY_SEQUENCE(SRIGHT), + KEY_SEQUENCE(SUP), + KEY_SEQUENCE(SDOWN), + KEY_SEQUENCE(MLEFT), + KEY_SEQUENCE(MRIGHT), + KEY_SEQUENCE(MUP), + KEY_SEQUENCE(MDOWN), + KEY_SEQUENCE(STAB), + KEY_SEQUENCE(M_p), + KEY_SEQUENCE(M_n), + { 0, NULL } +}; + +#define KEY_SEQUNCE_NOT_FINISHED -1 +#define KEY_C_C 3 +#define KEY_C_D 4 +#define KEY_C_L 12 + +#define isseqence(c) ((c) == 0x1B) + +/* + * Number of characters that consist of ANSI sequence + * Should not be less then longest string in ansi_sequences + */ +#define MAX_ASCII_SEQUENCE 10 + +static char current_sequence[MAX_ASCII_SEQUENCE]; +static int current_sequence_len = -1; + +/* single line typed by user goes here */ +static char line_buf[LINE_BUF_MAX]; +/* index of cursor in input line */ +static int line_buf_ix = 0; +/* current length of input line */ +static int line_len = 0; + +/* line index used for fetching lines from history */ +static int line_index = 0; + +static char prompt_buf[10] = "> "; +static const char *const noprompt = ""; +static const char *current_prompt = prompt_buf; +static const char *prompt = prompt_buf; +/* + * Moves cursor to right or left + * + * n - positive - moves cursor right + * n - negative - moves cursor left + */ +static void terminal_move_cursor(int n) +{ + if (n < 0) { + for (; n < 0; n++) + putchar('\b'); + } else if (n > 0) { + printf("%*s", n, line_buf + line_buf_ix); + } +} + +/* Draw command line */ +void terminal_draw_command_line(void) +{ + /* + * this needs to be checked here since line_buf is not cleared + * before parsing event though line_len and line_buf_ix are + */ + if (line_len > 0) + printf("%s%s", prompt, line_buf); + else + printf("%s", prompt); + + /* move cursor to it's place */ + terminal_move_cursor(line_buf_ix - line_len); +} + +/* inserts string into command line at cursor position */ +void terminal_insert_into_command_line(const char *p) +{ + int len = strlen(p); + + if (line_len == line_buf_ix) { + strcat(line_buf, p); + printf("%s", p); + line_len = line_len + len; + line_buf_ix = line_len; + } else { + memmove(line_buf + line_buf_ix + len, + line_buf + line_buf_ix, line_len - line_buf_ix + 1); + memmove(line_buf + line_buf_ix, p, len); + printf("%s", line_buf + line_buf_ix); + line_buf_ix += len; + line_len += len; + terminal_move_cursor(line_buf_ix - line_len); + } +} + +/* Prints string and redraws command line */ +int terminal_print(const char *format, ...) +{ + va_list args; + int ret; + + va_start(args, format); + + ret = terminal_vprint(format, args); + + va_end(args); + return ret; +} + +/* Prints string and redraws command line */ +int terminal_vprint(const char *format, va_list args) +{ + int ret; + + printf("\r%*s\r", (int) line_len + 1, " "); + + ret = vprintf(format, args); + + terminal_draw_command_line(); + + fflush(stdout); + + return ret; +} + +/* + * Call this when text in line_buf was changed + * and line needs to be redrawn + */ +static void terminal_line_replaced(void) +{ + int len = strlen(line_buf); + + /* line is shorter that previous */ + if (len < line_len) { + /* if new line is shorter move cursor to end of new end */ + while (line_buf_ix > len) { + putchar('\b'); + line_buf_ix--; + } + + /* If cursor was not at the end, move it to the end */ + if (line_buf_ix < line_len) + printf("%.*s", line_len - line_buf_ix, + line_buf + line_buf_ix); + /* over write end of previous line */ + while (line_len >= len++) + putchar(' '); + } + + /* draw new line */ + printf("\r%s%s", prompt, line_buf); + /* set up indexes to new line */ + line_len = strlen(line_buf); + line_buf_ix = line_len; + fflush(stdout); +} + +static void terminal_clear_line(void) +{ + line_buf[0] = '\0'; + terminal_line_replaced(); +} + +static void terminal_clear_screen(void) +{ + line_buf[0] = '\0'; + line_buf_ix = 0; + line_len = 0; + + printf("\x1b[2J\x1b[1;1H%s", prompt); +} + +static void terminal_delete_char(void) +{ + /* delete character under cursor if not at the very end */ + if (line_buf_ix >= line_len) + return; + /* + * Prepare buffer with one character missing + * trailing 0 is moved + */ + line_len--; + memmove(line_buf + line_buf_ix, line_buf + line_buf_ix + 1, + line_len - line_buf_ix + 1); + /* print rest of line from current cursor position */ + printf("%s \b", line_buf + line_buf_ix); + /* move back cursor */ + terminal_move_cursor(line_buf_ix - line_len); +} + +/* + * Function tries to replace current line with specified line in history + * new_line_index - new line to show, -1 to show oldest + */ +static void terminal_get_line_from_history(int new_line_index) +{ + new_line_index = history_get_line(new_line_index, + line_buf, LINE_BUF_MAX); + + if (new_line_index >= 0) { + terminal_line_replaced(); + line_index = new_line_index; + } +} + +/* + * Function searches history back or forward for command line that starts + * with characters up to cursor position + * + * back - true - searches backward + * back - false - searches forward (more recent commands) + */ +static void terminal_match_hitory(bool back) +{ + char buf[line_buf_ix + 1]; + int line; + int matching_line = -1; + int dir = back ? 1 : -1; + + line = line_index + dir; + while (matching_line == -1 && line >= 0) { + int new_line_index; + + new_line_index = history_get_line(line, buf, line_buf_ix + 1); + if (new_line_index < 0) + break; + + if (0 == strncmp(line_buf, buf, line_buf_ix)) + matching_line = line; + line += dir; + } + + if (matching_line >= 0) { + int pos = line_buf_ix; + terminal_get_line_from_history(matching_line); + /* move back to cursor position to original place */ + line_buf_ix = pos; + terminal_move_cursor(pos - line_len); + } +} + +/* + * Converts terminal character sequences to single value representing + * keyboard keys + */ +static int terminal_convert_sequence(int c) +{ + int i; + + /* Not in sequence yet? */ + if (current_sequence_len == -1) { + /* Is ansi sequence detected by 0x1B ? */ + if (isseqence(c)) { + current_sequence_len++; + return KEY_SEQUNCE_NOT_FINISHED; + } + + return c; + } + + /* Inside sequence */ + current_sequence[current_sequence_len++] = c; + current_sequence[current_sequence_len] = '\0'; + for (i = 0; ansii_sequnces[i].code; ++i) { + /* Matches so far? */ + if (0 != strncmp(current_sequence, ansii_sequnces[i].sequence, + current_sequence_len)) + continue; + + /* Matches as a whole? */ + if (ansii_sequnces[i].sequence[current_sequence_len] == 0) { + current_sequence_len = -1; + return ansii_sequnces[i].code; + } + + /* partial match (not whole sequence yet) */ + return KEY_SEQUNCE_NOT_FINISHED; + } + + terminal_print("ansi char 0x%X %c\n", c); + /* + * Sequence does not match + * mark that no in sequence any more, return char + */ + current_sequence_len = -1; + return c; +} + +typedef void (*terminal_action)(int c, line_callback process_line); + +#define TERMINAL_ACTION(n) \ + static void n(int c, void (*process_line)(char *line)) + +TERMINAL_ACTION(terminal_action_null) +{ +} + +/* Mapping between keys and function */ +typedef struct { + int key; + terminal_action func; +} KeyAction; + +int action_keys[] = { + KEY_SEQUNCE_NOT_FINISHED, + KEY_LEFT, + KEY_RIGHT, + KEY_HOME, + KEY_END, + KEY_DELETE, + KEY_CLEFT, + KEY_CRIGHT, + KEY_SUP, + KEY_SDOWN, + KEY_UP, + KEY_DOWN, + KEY_BACKSPACE, + KEY_INSERT, + KEY_PGUP, + KEY_PGDOWN, + KEY_CUP, + KEY_CDOWN, + KEY_SLEFT, + KEY_SRIGHT, + KEY_MLEFT, + KEY_MRIGHT, + KEY_MUP, + KEY_MDOWN, + KEY_STAB, + KEY_M_n, + KEY_M_p, + KEY_C_C, + KEY_C_D, + KEY_C_L, + '\t', + '\r', + '\n', +}; + +#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) + +/* + * current_actions holds all recognizable kes and actions for them + * additional element (index 0) is used for default action + */ +static KeyAction current_actions[NELEM(action_keys) + 1]; + +/* KeyAction comparator by key, for qsort and bsearch */ +static int KeyActionKeyCompare(const void *a, const void *b) +{ + return ((const KeyAction *) a)->key - ((const KeyAction *) b)->key; +} + +/* Find action by key, NULL if no action for this key */ +static KeyAction *terminal_get_action(int key) +{ + KeyAction a = { .key = key }; + + return bsearch(&a, current_actions + 1, NELEM(action_keys), sizeof(a), + KeyActionKeyCompare); +} + +/* Sets new set of actions to use */ +static void terminal_set_actions(const KeyAction *actions) +{ + int i; + + /* Make map with empty function for every key */ + for (i = 0; i < NELEM(action_keys); ++i) { + /* + * + 1 due to 0 index reserved for default action that is + * called for non mapped key + */ + current_actions[i + 1].key = action_keys[i]; + current_actions[i + 1].func = terminal_action_null; + } + + /* Sort action from 1 (index 0 - default action) */ + qsort(current_actions + 1, NELEM(action_keys), sizeof(KeyAction), + KeyActionKeyCompare); + /* Set default action (first in array) */ + current_actions[0] = *actions++; + + /* Copy rest of actions into their places */ + for (; actions->key; ++actions) { + KeyAction *place = terminal_get_action(actions->key); + + if (place) + place->func = actions->func; + } +} + +TERMINAL_ACTION(terminal_action_left) +{ + /* if not at the beginning move to previous character */ + if (line_buf_ix <= 0) + return; + line_buf_ix--; + terminal_move_cursor(-1); +} + +TERMINAL_ACTION(terminal_action_right) +{ + /* + * If not at the end, just print current character + * and modify position + */ + if (line_buf_ix < line_len) + putchar(line_buf[line_buf_ix++]); +} + +TERMINAL_ACTION(terminal_action_home) +{ + /* move to beginning of line and update position */ + printf("\r%s", prompt); + line_buf_ix = 0; +} + +TERMINAL_ACTION(terminal_action_end) +{ + /* if not at the end of line */ + if (line_buf_ix < line_len) { + /* print everything from cursor */ + printf("%s", line_buf + line_buf_ix); + /* just modify current position */ + line_buf_ix = line_len; + } +} + +TERMINAL_ACTION(terminal_action_del) +{ + terminal_delete_char(); +} + +TERMINAL_ACTION(terminal_action_word_left) +{ + int old_pos; + /* + * Move by word left + * + * Are we at the beginning of line? + */ + if (line_buf_ix <= 0) + return; + + old_pos = line_buf_ix; + line_buf_ix--; + /* skip spaces left */ + while (line_buf_ix && isspace(line_buf[line_buf_ix])) + line_buf_ix--; + + /* skip all non spaces to the left */ + while (line_buf_ix > 0 && + !isspace(line_buf[line_buf_ix - 1])) + line_buf_ix--; + + /* move cursor to new position */ + terminal_move_cursor(line_buf_ix - old_pos); +} + +TERMINAL_ACTION(terminal_action_word_right) +{ + int old_pos; + /* + * Move by word right + * + * are we at the end of line? + */ + if (line_buf_ix >= line_len) + return; + + old_pos = line_buf_ix; + /* skip all spaces */ + while (line_buf_ix < line_len && isspace(line_buf[line_buf_ix])) + line_buf_ix++; + + /* skip all non spaces */ + while (line_buf_ix < line_len && !isspace(line_buf[line_buf_ix])) + line_buf_ix++; + /* + * Move cursor to right by printing text + * between old cursor and new + */ + if (line_buf_ix > old_pos) + printf("%.*s", (int) (line_buf_ix - old_pos), + line_buf + old_pos); +} + +TERMINAL_ACTION(terminal_action_history_begin) +{ + terminal_get_line_from_history(-1); +} + +TERMINAL_ACTION(terminal_action_history_end) +{ + if (line_index > 0) + terminal_get_line_from_history(0); +} + +TERMINAL_ACTION(terminal_action_history_up) +{ + terminal_get_line_from_history(line_index + 1); +} + +TERMINAL_ACTION(terminal_action_history_down) +{ + if (line_index > 0) + terminal_get_line_from_history(line_index - 1); +} + +TERMINAL_ACTION(terminal_action_tab) +{ + /* tab processing */ + process_tab(line_buf, line_buf_ix); +} + + +TERMINAL_ACTION(terminal_action_backspace) +{ + if (line_buf_ix <= 0) + return; + + if (line_buf_ix == line_len) { + printf("\b \b"); + line_len = --line_buf_ix; + line_buf[line_len] = 0; + } else { + putchar('\b'); + line_buf_ix--; + line_len--; + memmove(line_buf + line_buf_ix, + line_buf + line_buf_ix + 1, + line_len - line_buf_ix + 1); + printf("%s \b", line_buf + line_buf_ix); + terminal_move_cursor(line_buf_ix - line_len); + } +} + +TERMINAL_ACTION(terminal_action_find_history_forward) +{ + /* Search history forward */ + terminal_match_hitory(false); +} + +TERMINAL_ACTION(terminal_action_find_history_backward) +{ + /* Search history forward */ + terminal_match_hitory(true); +} + +TERMINAL_ACTION(terminal_action_ctrl_c) +{ + terminal_clear_line(); +} + +TERMINAL_ACTION(terminal_action_ctrl_d) +{ + if (line_len > 0) { + terminal_delete_char(); + } else { + puts(""); + exit(0); + } +} + +TERMINAL_ACTION(terminal_action_clear_screen) +{ + terminal_clear_screen(); +} + +TERMINAL_ACTION(terminal_action_enter) +{ + /* + * On new line add line to history + * forget history position + */ + history_add_line(line_buf); + line_len = 0; + line_buf_ix = 0; + line_index = -1; + /* print new line */ + putchar(c); + prompt = noprompt; + process_line(line_buf); + /* clear current line */ + line_buf[0] = '\0'; + prompt = current_prompt; + printf("%s", prompt); +} + +TERMINAL_ACTION(terminal_action_default) +{ + char str[2] = { c, 0 }; + + if (!isprint(c)) + /* + * TODO: remove this print once all meaningful sequences + * are identified + */ + printf("char-0x%02x\n", c); + else if (line_buf_ix < LINE_BUF_MAX - 1) + terminal_insert_into_command_line(str); +} + +/* Callback to call when user hit enter during prompt for */ +static line_callback prompt_callback; + +static KeyAction normal_actions[] = { + { 0, terminal_action_default }, + { KEY_LEFT, terminal_action_left }, + { KEY_RIGHT, terminal_action_right }, + { KEY_HOME, terminal_action_home }, + { KEY_END, terminal_action_end }, + { KEY_DELETE, terminal_action_del }, + { KEY_CLEFT, terminal_action_word_left }, + { KEY_CRIGHT, terminal_action_word_right }, + { KEY_SUP, terminal_action_history_begin }, + { KEY_SDOWN, terminal_action_history_end }, + { KEY_UP, terminal_action_history_up }, + { KEY_DOWN, terminal_action_history_down }, + { '\t', terminal_action_tab }, + { KEY_BACKSPACE, terminal_action_backspace }, + { KEY_M_n, terminal_action_find_history_forward }, + { KEY_M_p, terminal_action_find_history_backward }, + { KEY_C_C, terminal_action_ctrl_c }, + { KEY_C_D, terminal_action_ctrl_d }, + { KEY_C_L, terminal_action_clear_screen }, + { '\r', terminal_action_enter }, + { '\n', terminal_action_enter }, + { 0, NULL }, +}; + +TERMINAL_ACTION(terminal_action_answer) +{ + putchar(c); + + terminal_set_actions(normal_actions); + /* Restore default prompt */ + current_prompt = prompt_buf; + + /* No prompt for prints */ + prompt = noprompt; + line_buf_ix = 0; + line_len = 0; + /* Call user function with what was typed */ + prompt_callback(line_buf); + + line_buf[0] = 0; + /* promot_callback could change current_prompt */ + prompt = current_prompt; + + printf("%s", prompt); +} + +TERMINAL_ACTION(terminal_action_prompt_ctrl_c) +{ + printf("^C\n"); + line_buf_ix = 0; + line_len = 0; + line_buf[0] = 0; + + current_prompt = prompt_buf; + prompt = current_prompt; + terminal_set_actions(normal_actions); + + printf("%s", prompt); +} + +static KeyAction prompt_actions[] = { + { 0, terminal_action_default }, + { KEY_LEFT, terminal_action_left }, + { KEY_RIGHT, terminal_action_right }, + { KEY_HOME, terminal_action_home }, + { KEY_END, terminal_action_end }, + { KEY_DELETE, terminal_action_del }, + { KEY_CLEFT, terminal_action_word_left }, + { KEY_CRIGHT, terminal_action_word_right }, + { KEY_BACKSPACE, terminal_action_backspace }, + { KEY_C_C, terminal_action_prompt_ctrl_c }, + { KEY_C_D, terminal_action_ctrl_d }, + { '\r', terminal_action_answer }, + { '\n', terminal_action_answer }, + { 0, NULL }, +}; + +void terminal_process_char(int c, line_callback process_line) +{ + KeyAction *a; + + c = terminal_convert_sequence(c); + + /* Get action for this key */ + a = terminal_get_action(c); + + /* No action found, get default one */ + if (a == NULL) + a = ¤t_actions[0]; + + a->func(c, process_line); + fflush(stdout); +} + +void terminal_prompt_for(const char *s, line_callback process_line) +{ + current_prompt = s; + if (prompt != noprompt) { + prompt = s; + terminal_clear_line(); + } + prompt_callback = process_line; + terminal_set_actions(prompt_actions); +} + +static struct termios origianl_tios; + +static void terminal_cleanup(void) +{ + tcsetattr(0, TCSANOW, &origianl_tios); +} + +void terminal_setup(void) +{ + struct termios tios; + + terminal_set_actions(normal_actions); + + tcgetattr(0, &origianl_tios); + tios = origianl_tios; + + /* + * Turn off echo since all editing is done by hand, + * Ctrl-c handled internally + */ + tios.c_lflag &= ~(ICANON | ECHO | BRKINT | IGNBRK); + tcsetattr(0, TCSANOW, &tios); + + /* Restore terminal at exit */ + atexit(terminal_cleanup); + + printf("%s", prompt); + fflush(stdout); +}
diff --git a/android/client/terminal.h b/android/client/terminal.h new file mode 100644 index 0000000..0e63936 --- /dev/null +++ b/android/client/terminal.h
@@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdarg.h> + +/* size of supported line */ +#define LINE_BUF_MAX 1024 + +enum key_codes { + KEY_BACKSPACE = 0x7F, + KEY_INSERT = 1000, /* arbitrary value */ + KEY_DELETE, + KEY_HOME, + KEY_END, + KEY_PGUP, + KEY_PGDOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_UP, + KEY_DOWN, + KEY_CLEFT, + KEY_CRIGHT, + KEY_CUP, + KEY_CDOWN, + KEY_SLEFT, + KEY_SRIGHT, + KEY_SUP, + KEY_SDOWN, + KEY_MLEFT, + KEY_MRIGHT, + KEY_MUP, + KEY_MDOWN, + KEY_STAB, + KEY_M_p, + KEY_M_n +}; + +typedef void (*line_callback)(char *); + +void terminal_setup(void); +int terminal_print(const char *format, ...); +int terminal_vprint(const char *format, va_list args); +void terminal_process_char(int c, line_callback process_line); +void terminal_insert_into_command_line(const char *p); +void terminal_draw_command_line(void); +void terminal_prompt_for(const char *s, line_callback process_line); + +void process_tab(const char *line, int len);
diff --git a/android/compat/readline/history.h b/android/compat/readline/history.h new file mode 100644 index 0000000..decc2f4 --- /dev/null +++ b/android/compat/readline/history.h
@@ -0,0 +1,31 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 1987-2011 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _HISTORY_H_ +#define _HISTORY_H_ + +static inline void add_history(const char *c) +{ +} + +#endif
diff --git a/android/compat/readline/readline.h b/android/compat/readline/readline.h new file mode 100644 index 0000000..aaf6f31 --- /dev/null +++ b/android/compat/readline/readline.h
@@ -0,0 +1,110 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 1987-2011 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _READLINE_H_ +#define _READLINE_H_ + +typedef void (*rl_vcpfunc_t)(char *c); +typedef void (*rl_compdisp_func_t)(char **c, int i, int j); +typedef char *(*rl_compentry_func_t)(const char *c, int i); +typedef char **(*rl_completion_func_t)(const char *c, int i, int j); + +#define RL_STATE_DONE 0x1000000 +#define RL_ISSTATE(x) (rl_readline_state & (x)) + +static int rl_end; +static int rl_point; +static int rl_readline_state; +static int rl_erase_empty_line; +static int rl_attempted_completion_over; +static char *rl_prompt; +static char *rl_line_buffer; +static rl_compdisp_func_t rl_completion_display_matches_hook; +static rl_completion_func_t rl_attempted_completion_function; + +static inline void rl_callback_handler_install(const char *c, rl_vcpfunc_t f) +{ + printf("readline not available\n"); + exit(1); +} + +static inline int rl_set_prompt(const char *c) +{ + return -1; +} + +static inline void rl_replace_line(const char *c, int i) +{ +} + +static inline void rl_redisplay(void) +{ +} + +static inline char **rl_completion_matches(const char *c, rl_compentry_func_t f) +{ + return NULL; +} + +static inline int rl_insert_text(const char *c) +{ + return -1; +} + +static inline int rl_crlf(void) +{ + return -1; +} + +static inline void rl_callback_read_char(void) +{ +} + +static inline int rl_message(const char *c, ...) +{ + return -1; +} + +static inline void rl_callback_handler_remove(void) +{ +} + +static inline char *rl_copy_text(int i, int j) +{ + return NULL; +} + +static inline void rl_save_prompt(void) +{ +} + +static inline void rl_restore_prompt(void) +{ +} + +static inline int rl_forced_update_display(void) +{ + return -1; +} + +#endif
diff --git a/android/compat/wordexp.h b/android/compat/wordexp.h new file mode 100644 index 0000000..ff1f21c --- /dev/null +++ b/android/compat/wordexp.h
@@ -0,0 +1,44 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 1991-2013 Free Software Foundation, Inc. + * + * + * 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 _WORDEXP_H_ +#define _WORDEXP_H_ + +#define WRDE_NOCMD 0 + +typedef struct { + size_t we_wordc; + char **we_wordv; + size_t we_offs; +} wordexp_t; + +static inline int wordexp(const char *c, wordexp_t *w, int _i) +{ + return -1; +} + +static inline void wordfree(wordexp_t *__wordexp) +{ +} + +#endif
diff --git a/android/cts.txt b/android/cts.txt new file mode 100644 index 0000000..f679263 --- /dev/null +++ b/android/cts.txt
@@ -0,0 +1,58 @@ +Android Compatibility Test Suite results + +Tested: 27-Nov-2014 +Android version: 5.0 +Kernel Version: 3.18 +CTS version: 5.0 R1 (android-5.0.0_r7) + +Note: +CTS reliable write GATT tests require using CTS from master branch +or https://android-review.googlesource.com/99354 applied. + +(*) Tests are disabled due to CTS quality issues. Link for reference: +https://android.googlesource.com/platform/cts/+/0a62e4a0a9910101ccf2ccc43f6%5E!/ + +------------------------------------------------------------------------------- +android.bluetooth.cts.BasicAdapterTest (automated tests) +Test Name Result Notes +------------------------------------------------------------------------------- +testAndroidTestCaseSetupProperly PASS +test_checkBluetoothAddress PASS +test_enableDisable PASS +test_getAddress PASS +test_getBondedDevices PASS +test_getDefaultAdapter PASS +test_getName PASS +test_getRemoteDevice PASS +test_listenUsingRfcommWithServiceRecord PASS +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +com.android.cts.verifier (manual tests) +Test Name Result Notes +------------------------------------------------------------------------------- +Toggle Bluetooth PASS +BLE Client Test: + connect N/A (*) + discover service N/A (*) + read/write characteristic N/A (*) + reliable write N/A (*) + notify characteristic N/A (*) + read/write descriptor N/A (*) + read RSSI N/A (*) + disconnect N/A (*) +BLE Server Test: + add service N/A (*) + connection N/A (*) + read characteristic request N/A (*) + write characteristic request N/A (*) + read descriptor request N/A (*) + write descriptor request N/A (*) + reliable write N/A (*) + disconnection N/A (*) +Insecure Client PASS +Insecure Server PASS +Secure Client PASS +Secure Server PASS +-------------------------------------------------------------------------------
diff --git a/android/cutils/properties.h b/android/cutils/properties.h new file mode 100644 index 0000000..0163eb5 --- /dev/null +++ b/android/cutils/properties.h
@@ -0,0 +1,95 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> + +#define PROPERTY_VALUE_MAX 32 +#define PROPERTY_KEY_MAX 32 + +#define BLUETOOTH_MODE_PROPERTY_NAME "persist.sys.bluetooth.mode" +#define BLUETOOTH_MODE_PROPERTY_HANDSFREE "persist.sys.bluetooth.handsfree" + +static inline int property_get(const char *key, char *value, + const char *default_value) +{ + const char *prop = NULL; + + if (!strcmp(key, BLUETOOTH_MODE_PROPERTY_NAME)) + prop = getenv("BLUETOOTH_MODE"); + + if (!strcmp(key, BLUETOOTH_MODE_PROPERTY_HANDSFREE)) + prop = getenv("BLUETOOTH_HANDSFREE_MODE"); + + if (!prop) + prop = default_value; + + if (prop) { + strncpy(value, prop, PROPERTY_VALUE_MAX); + + value[PROPERTY_VALUE_MAX - 1] = '\0'; + + return strlen(value); + } + + return 0; +} + +/* property_set: returns 0 on success, < 0 on failure +*/ +static inline int property_set(const char *key, const char *value) +{ + static const char SYSTEM_SOCKET_PATH[] = "\0android_system"; + + struct sockaddr_un addr; + char msg[256]; + int fd, len; + + fd = socket(PF_LOCAL, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, SYSTEM_SOCKET_PATH, sizeof(SYSTEM_SOCKET_PATH)); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + return 0; + } + + len = snprintf(msg, sizeof(msg), "%s=%s", key, value); + + if (send(fd, msg, len + 1, 0) < 0) { + close(fd); + return -1; + } + + close(fd); + + return 0; +}
diff --git a/android/gatt.c b/android/gatt.c new file mode 100644 index 0000000..28635ed --- /dev/null +++ b/android/gatt.c
@@ -0,0 +1,7193 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <glib.h> +#include <errno.h> +#include <sys/socket.h> + +#include "ipc.h" +#include "ipc-common.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" +#include "bluetooth.h" +#include "gatt.h" +#include "src/log.h" +#include "hal-msg.h" +#include "utils.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "btio/btio.h" + +/* set according to Android bt_gatt_client.h */ +#define GATT_MAX_ATTR_LEN 600 + +#define GATT_SUCCESS 0x00000000 +#define GATT_FAILURE 0x00000101 + +#define BASE_UUID16_OFFSET 12 + +#define GATT_PERM_READ 0x00000001 +#define GATT_PERM_READ_ENCRYPTED 0x00000002 +#define GATT_PERM_READ_MITM 0x00000004 +#define GATT_PERM_READ_AUTHORIZATION 0x00000008 +#define GATT_PERM_WRITE 0x00000100 +#define GATT_PERM_WRITE_ENCRYPTED 0x00000200 +#define GATT_PERM_WRITE_MITM 0x00000400 +#define GATT_PERM_WRITE_AUTHORIZATION 0x00000800 +#define GATT_PERM_WRITE_SIGNED 0x00010000 +#define GATT_PERM_WRITE_SIGNED_MITM 0x00020000 +#define GATT_PERM_NONE 0x10000000 + +#define GATT_PAIR_CONN_TIMEOUT 30 +#define GATT_CONN_TIMEOUT 2 + +static const uint8_t BLUETOOTH_UUID[] = { + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +typedef enum { + DEVICE_DISCONNECTED = 0, + DEVICE_CONNECT_INIT, /* connection procedure initiated */ + DEVICE_CONNECT_READY, /* dev found during LE scan */ + DEVICE_CONNECTED, /* connection has been established */ +} gatt_device_state_t; + +static const char *device_state_str[] = { + "DISCONNECTED", + "CONNECT INIT", + "CONNECT READY", + "CONNECTED", +}; + +struct pending_trans_data { + unsigned int id; + uint8_t opcode; + struct gatt_db_attribute *attrib; + unsigned int serial_id; +}; + +struct gatt_app { + int32_t id; + uint8_t uuid[16]; + + gatt_type_t type; + + /* Valid for client applications */ + struct queue *notifications; + + gatt_conn_cb_t func; +}; + +struct element_id { + bt_uuid_t uuid; + uint8_t instance; +}; + +struct descriptor { + struct element_id id; + uint16_t handle; +}; + +struct characteristic { + struct element_id id; + struct gatt_char ch; + uint16_t end_handle; + + struct queue *descriptors; +}; + +struct service { + struct element_id id; + struct gatt_primary prim; + struct gatt_included incl; + + bool primary; + + struct queue *chars; + struct queue *included; /* Valid only for primary services */ + bool incl_search_done; +}; + +struct notification_data { + struct hal_gatt_srvc_id service; + struct hal_gatt_gatt_id ch; + struct app_connection *conn; + guint notif_id; + guint ind_id; + int ref; +}; + +struct gatt_device { + bdaddr_t bdaddr; + + gatt_device_state_t state; + + GAttrib *attrib; + GIOChannel *att_io; + struct queue *services; + bool partial_srvc_search; + + guint watch_id; + guint server_id; + guint ind_id; + + int ref; + + struct queue *autoconnect_apps; + + struct queue *pending_requests; +}; + +struct app_connection { + struct gatt_device *device; + struct gatt_app *app; + struct queue *transactions; + int32_t id; + + guint timeout_id; + + bool wait_execute_write; +}; + +struct service_sdp { + int32_t service_handle; + uint32_t sdp_handle; +}; + +static struct ipc *hal_ipc = NULL; +static bdaddr_t adapter_addr; +static bool scanning = false; +static unsigned int advertising_cnt = 0; + +static struct queue *gatt_apps = NULL; +static struct queue *gatt_devices = NULL; +static struct queue *app_connections = NULL; + +static struct queue *services_sdp = NULL; + +static struct queue *listen_apps = NULL; +static struct gatt_db *gatt_db = NULL; + +static struct gatt_db_attribute *service_changed_attrib = NULL; + +static GIOChannel *le_io = NULL; +static GIOChannel *bredr_io = NULL; + +static uint32_t gatt_sdp_handle = 0; +static uint32_t gap_sdp_handle = 0; +static uint32_t dis_sdp_handle = 0; + +static struct bt_crypto *crypto = NULL; + +static int test_client_if = 0; +static const uint8_t TEST_UUID[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04 +}; + +static bool is_bluetooth_uuid(const uint8_t *uuid) +{ + int i; + + for (i = 0; i < 16; i++) { + /* ignore minimal uuid (16) value */ + if (i == 12 || i == 13) + continue; + + if (uuid[i] != BLUETOOTH_UUID[i]) + return false; + } + + return true; +} + +static void android2uuid(const uint8_t *uuid, bt_uuid_t *dst) +{ + if (is_bluetooth_uuid(uuid)) { + /* copy 16 bit uuid value from full android 128bit uuid */ + dst->type = BT_UUID16; + dst->value.u16 = (uuid[13] << 8) + uuid[12]; + } else { + int i; + + dst->type = BT_UUID128; + for (i = 0; i < 16; i++) + dst->value.u128.data[i] = uuid[15 - i]; + } +} + +static void uuid2android(const bt_uuid_t *src, uint8_t *uuid) +{ + bt_uuid_t uu128; + uint8_t i; + + if (src->type != BT_UUID128) { + bt_uuid_to_uuid128(src, &uu128); + src = &uu128; + } + + for (i = 0; i < 16; i++) + uuid[15 - i] = src->value.u128.data[i]; +} + +static void hal_srvc_id_to_element_id(const struct hal_gatt_srvc_id *from, + struct element_id *to) +{ + to->instance = from->inst_id; + android2uuid(from->uuid, &to->uuid); +} + +static void element_id_to_hal_srvc_id(const struct element_id *from, + uint8_t primary, + struct hal_gatt_srvc_id *to) +{ + to->is_primary = primary; + to->inst_id = from->instance; + uuid2android(&from->uuid, to->uuid); +} + +static void hal_gatt_id_to_element_id(const struct hal_gatt_gatt_id *from, + struct element_id *to) +{ + to->instance = from->inst_id; + android2uuid(from->uuid, &to->uuid); +} + +static void element_id_to_hal_gatt_id(const struct element_id *from, + struct hal_gatt_gatt_id *to) +{ + to->inst_id = from->instance; + uuid2android(&from->uuid, to->uuid); +} + +static void destroy_characteristic(void *data) +{ + struct characteristic *chars = data; + + if (!chars) + return; + + queue_destroy(chars->descriptors, free); + free(chars); +} + +static void destroy_service(void *data) +{ + struct service *srvc = data; + + if (!srvc) + return; + + queue_destroy(srvc->chars, destroy_characteristic); + + /* + * Included services we keep on two queues. + * 1. On the same queue with primary services. + * 2. On the queue inside primary service. + * So we need to free service memory only once but we need to destroy + * two queues + */ + queue_destroy(srvc->included, NULL); + + free(srvc); +} + +static bool match_app_by_uuid(const void *data, const void *user_data) +{ + const uint8_t *exp_uuid = user_data; + const struct gatt_app *client = data; + + return !memcmp(exp_uuid, client->uuid, sizeof(client->uuid)); +} + +static bool match_app_by_id(const void *data, const void *user_data) +{ + int32_t exp_id = PTR_TO_INT(user_data); + const struct gatt_app *client = data; + + return client->id == exp_id; +} + +static struct gatt_app *find_app_by_id(int32_t id) +{ + return queue_find(gatt_apps, match_app_by_id, INT_TO_PTR(id)); +} + +static bool match_device_by_bdaddr(const void *data, const void *user_data) +{ + const struct gatt_device *dev = data; + const bdaddr_t *addr = user_data; + + return !bacmp(&dev->bdaddr, addr); +} + +static bool match_device_by_state(const void *data, const void *user_data) +{ + const struct gatt_device *dev = data; + + if (dev->state != PTR_TO_UINT(user_data)) + return false; + + return true; +} + +static bool match_pending_device(const void *data, const void *user_data) +{ + const struct gatt_device *dev = data; + + if ((dev->state == DEVICE_CONNECT_INIT) || + (dev->state == DEVICE_CONNECT_READY)) + return true; + + return false; +} + +static bool match_connection_by_id(const void *data, const void *user_data) +{ + const struct app_connection *conn = data; + const int32_t id = PTR_TO_INT(user_data); + + return conn->id == id; +} + +static bool match_connection_by_device_and_app(const void *data, + const void *user_data) +{ + const struct app_connection *conn = data; + const struct app_connection *match = user_data; + + return conn->device == match->device && conn->app == match->app; +} + +static struct app_connection *find_connection_by_id(int32_t conn_id) +{ + struct app_connection *conn; + + conn = queue_find(app_connections, match_connection_by_id, + INT_TO_PTR(conn_id)); + if (conn && conn->device->state == DEVICE_CONNECTED) + return conn; + + return NULL; +} + +static bool match_connection_by_device(const void *data, const void *user_data) +{ + const struct app_connection *conn = data; + const struct gatt_device *dev = user_data; + + return conn->device == dev; +} + +static bool match_connection_by_app(const void *data, const void *user_data) +{ + const struct app_connection *conn = data; + const struct gatt_app *app = user_data; + + return conn->app == app; +} + +static struct gatt_device *find_device_by_addr(const bdaddr_t *addr) +{ + return queue_find(gatt_devices, match_device_by_bdaddr, addr); +} + +static struct gatt_device *find_pending_device(void) +{ + return queue_find(gatt_devices, match_pending_device, NULL); +} + +static struct gatt_device *find_device_by_state(uint32_t state) +{ + return queue_find(gatt_devices, match_device_by_state, + UINT_TO_PTR(state)); +} + +static bool match_srvc_by_element_id(const void *data, const void *user_data) +{ + const struct element_id *exp_id = user_data; + const struct service *service = data; + + if (service->id.instance == exp_id->instance) + return !bt_uuid_cmp(&service->id.uuid, &exp_id->uuid); + + return false; +} + +static bool match_srvc_by_higher_inst_id(const void *data, + const void *user_data) +{ + const struct service *s = data; + uint8_t inst_id = PTR_TO_INT(user_data); + + /* For now we match inst_id as it is unique */ + return inst_id < s->id.instance; +} + +static bool match_srvc_by_bt_uuid(const void *data, const void *user_data) +{ + const bt_uuid_t *exp_uuid = user_data; + const struct service *service = data; + + return !bt_uuid_cmp(exp_uuid, &service->id.uuid); +} + +static bool match_srvc_by_range(const void *data, const void *user_data) +{ + const struct service *srvc = data; + const struct att_range *range = user_data; + + return !memcmp(&srvc->prim.range, range, sizeof(srvc->prim.range)); +} + +static bool match_char_by_higher_inst_id(const void *data, + const void *user_data) +{ + const struct characteristic *ch = data; + uint8_t inst_id = PTR_TO_INT(user_data); + + /* For now we match inst_id as it is unique, we'll match uuids later */ + return inst_id < ch->id.instance; +} + +static bool match_descr_by_element_id(const void *data, const void *user_data) +{ + const struct element_id *exp_id = user_data; + const struct descriptor *descr = data; + + if (exp_id->instance == descr->id.instance) + return !bt_uuid_cmp(&descr->id.uuid, &exp_id->uuid); + + return false; +} + +static bool match_descr_by_higher_inst_id(const void *data, + const void *user_data) +{ + const struct descriptor *descr = data; + uint8_t instance = PTR_TO_INT(user_data); + + /* For now we match instance as it is unique */ + return instance < descr->id.instance; +} + +static bool match_notification(const void *a, const void *b) +{ + const struct notification_data *a1 = a; + const struct notification_data *b1 = b; + + if (a1->conn != b1->conn) + return false; + + if (memcmp(&a1->ch, &b1->ch, sizeof(a1->ch))) + return false; + + if (memcmp(&a1->service, &b1->service, sizeof(a1->service))) + return false; + + return true; +} + +static bool match_char_by_element_id(const void *data, const void *user_data) +{ + const struct element_id *exp_id = user_data; + const struct characteristic *chars = data; + + if (exp_id->instance == chars->id.instance) + return !bt_uuid_cmp(&chars->id.uuid, &exp_id->uuid); + + return false; +} + +static void destroy_notification(void *data) +{ + struct notification_data *notification = data; + struct gatt_app *app; + + if (!notification) + return; + + if (--notification->ref) + return; + + app = notification->conn->app; + queue_remove_if(app->notifications, match_notification, notification); + free(notification); +} + +static void unregister_notification(void *data) +{ + struct notification_data *notification = data; + struct gatt_device *dev = notification->conn->device; + + /* + * No device means it was already disconnected and client cleanup was + * triggered afterwards, but once client unregisters, device stays if + * used by others. Then just unregister single handle. + */ + if (!queue_find(gatt_devices, NULL, dev)) + return; + + if (notification->notif_id && dev) + g_attrib_unregister(dev->attrib, notification->notif_id); + + if (notification->ind_id && dev) + g_attrib_unregister(dev->attrib, notification->ind_id); +} + +static void device_set_state(struct gatt_device *dev, uint32_t state) +{ + char bda[18]; + + if (dev->state == state) + return; + + ba2str(&dev->bdaddr, bda); + DBG("gatt: Device %s state changed %s -> %s", bda, + device_state_str[dev->state], device_state_str[state]); + + dev->state = state; +} + +static bool auto_connect_le(struct gatt_device *dev) +{ + /* For LE devices use auto connect feature if possible */ + if (bt_kernel_conn_control()) { + if (!bt_auto_connect_add(bt_get_id_addr(&dev->bdaddr, NULL))) + return false; + } else { + /* Trigger discovery if not already started */ + if (!scanning && !bt_le_discovery_start()) { + error("gatt: Could not start scan"); + return false; + } + } + + device_set_state(dev, DEVICE_CONNECT_INIT); + return true; +} + +static void connection_cleanup(struct gatt_device *device) +{ + if (device->watch_id) { + g_source_remove(device->watch_id); + device->watch_id = 0; + } + + if (device->att_io) { + g_io_channel_shutdown(device->att_io, FALSE, NULL); + g_io_channel_unref(device->att_io); + device->att_io = NULL; + } + + if (device->attrib) { + GAttrib *attrib = device->attrib; + + if (device->server_id > 0) + g_attrib_unregister(device->attrib, device->server_id); + + if (device->ind_id > 0) + g_attrib_unregister(device->attrib, device->ind_id); + + device->attrib = NULL; + g_attrib_cancel_all(attrib); + g_attrib_unref(attrib); + } + + /* + * If device was in connection_pending or connectable state we + * search device list if we should stop the scan. + */ + if (!scanning && (device->state == DEVICE_CONNECT_INIT || + device->state == DEVICE_CONNECT_READY)) { + if (!find_pending_device()) + bt_le_discovery_stop(NULL); + } + + /* If device is not bonded service cache should be refreshed */ + if (!bt_device_is_bonded(&device->bdaddr)) + queue_remove_all(device->services, NULL, NULL, destroy_service); + + device_set_state(device, DEVICE_DISCONNECTED); + + if (!queue_isempty(device->autoconnect_apps)) + auto_connect_le(device); + else + bt_auto_connect_remove(&device->bdaddr); +} + +static void destroy_gatt_app(void *data) +{ + struct gatt_app *app = data; + + if (!app) + return; + + /* + * First we want to get all notifications and unregister them. + * We don't pass unregister_notification to queue_destroy, + * because destroy notification performs operations on queue + * too. So remove all elements and then destroy queue. + */ + + if (app->type == GATT_CLIENT) + while (queue_peek_head(app->notifications)) { + struct notification_data *notification; + + notification = queue_pop_head(app->notifications); + unregister_notification(notification); + } + + queue_destroy(app->notifications, free); + + free(app); +} + +struct pending_request { + struct gatt_db_attribute *attrib; + int length; + uint8_t *value; + uint16_t offset; + + uint8_t *filter_value; + uint16_t filter_vlen; + + bool completed; + uint8_t error; +}; + +static void destroy_pending_request(void *data) +{ + struct pending_request *entry = data; + + if (!entry) + return; + + free(entry->value); + free(entry->filter_value); + free(entry); +} + +static void destroy_device(void *data) +{ + struct gatt_device *dev = data; + + if (!dev) + return; + + queue_destroy(dev->services, destroy_service); + queue_destroy(dev->pending_requests, destroy_pending_request); + queue_destroy(dev->autoconnect_apps, NULL); + + bt_auto_connect_remove(&dev->bdaddr); + + free(dev); +} + +static struct gatt_device *device_ref(struct gatt_device *device) +{ + if (!device) + return NULL; + + device->ref++; + + return device; +} + +static void device_unref(struct gatt_device *device) +{ + if (!device) + return; + + if (--device->ref) + return; + + destroy_device(device); +} + +static struct gatt_device *create_device(const bdaddr_t *addr) +{ + struct gatt_device *dev; + + dev = new0(struct gatt_device, 1); + + bacpy(&dev->bdaddr, addr); + + dev->services = queue_new(); + dev->autoconnect_apps = queue_new(); + dev->pending_requests = queue_new(); + + queue_push_head(gatt_devices, dev); + + return device_ref(dev); +} + +static void send_client_connect_status_notify(struct app_connection *conn, + int32_t status) +{ + struct hal_ev_gatt_client_connect ev; + + if (conn->app->func) { + conn->app->func(&conn->device->bdaddr, + status == GATT_SUCCESS ? 0 : -ENOTCONN, + conn->device->attrib); + return; + } + + ev.client_if = conn->app->id; + ev.conn_id = conn->id; + ev.status = status; + + bdaddr2android(&conn->device->bdaddr, &ev.bda); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, HAL_EV_GATT_CLIENT_CONNECT, + sizeof(ev), &ev); +} + +static void send_server_connection_state_notify(struct app_connection *conn, + bool connected) +{ + struct hal_ev_gatt_server_connection ev; + + if (conn->app->func) { + conn->app->func(&conn->device->bdaddr, + connected ? 0 : -ENOTCONN, + conn->device->attrib); + return; + } + + ev.server_if = conn->app->id; + ev.conn_id = conn->id; + ev.connected = connected; + + bdaddr2android(&conn->device->bdaddr, &ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_CONNECTION, sizeof(ev), &ev); +} + +static void send_client_disconnect_status_notify(struct app_connection *conn, + int32_t status) +{ + struct hal_ev_gatt_client_disconnect ev; + + if (conn->app->func) { + conn->app->func(&conn->device->bdaddr, -ENOTCONN, + conn->device->attrib); + return; + } + + ev.client_if = conn->app->id; + ev.conn_id = conn->id; + ev.status = status; + + bdaddr2android(&conn->device->bdaddr, &ev.bda); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_DISCONNECT, sizeof(ev), &ev); + +} + +static void notify_app_disconnect_status(struct app_connection *conn, + int32_t status) +{ + if (!conn->app) + return; + + if (conn->app->type == GATT_CLIENT) + send_client_disconnect_status_notify(conn, status); + else + send_server_connection_state_notify(conn, !!status); +} + +static void notify_app_connect_status(struct app_connection *conn, + int32_t status) +{ + if (!conn->app) + return; + + if (conn->app->type == GATT_CLIENT) + send_client_connect_status_notify(conn, status); + else + send_server_connection_state_notify(conn, !status); +} + +static void destroy_connection(void *data) +{ + struct app_connection *conn = data; + + if (!conn) + return; + + if (conn->timeout_id > 0) + g_source_remove(conn->timeout_id); + + switch (conn->device->state) { + case DEVICE_CONNECTED: + notify_app_disconnect_status(conn, GATT_SUCCESS); + break; + case DEVICE_CONNECT_INIT: + case DEVICE_CONNECT_READY: + notify_app_connect_status(conn, GATT_FAILURE); + break; + case DEVICE_DISCONNECTED: + break; + } + + if (!queue_find(app_connections, match_connection_by_device, + conn->device)) + connection_cleanup(conn->device); + + queue_destroy(conn->transactions, free); + device_unref(conn->device); + free(conn); +} + +static gboolean disconnected_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct gatt_device *dev = user_data; + int sock, err = 0; + socklen_t len; + + sock = g_io_channel_unix_get_fd(io); + len = sizeof(err); + if (!getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len)) + DBG("%s (%d)", strerror(err), err); + + queue_remove_all(app_connections, match_connection_by_device, dev, + destroy_connection); + + return FALSE; +} + +static bool get_local_mtu(struct gatt_device *dev, uint16_t *mtu) +{ + GIOChannel *io; + uint16_t imtu, omtu; + + io = g_attrib_get_channel(dev->attrib); + + if (!bt_io_get(io, NULL, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID)) { + error("gatt: Failed to get local MTU"); + return false; + } + + /* + * Limit MTU to MIN(IMTU, OMTU). This is to avoid situation where + * local OMTU < MIN(remote MTU, IMTU) + */ + if (mtu) + *mtu = MIN(imtu, omtu); + + return true; +} + +static void notify_client_mtu_change(struct app_connection *conn, bool success) +{ + struct hal_ev_gatt_client_configure_mtu ev; + size_t mtu; + + g_attrib_get_buffer(conn->device->attrib, &mtu); + + ev.conn_id = conn->id; + ev.status = success ? GATT_SUCCESS : GATT_FAILURE; + ev.mtu = mtu; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_CONFIGURE_MTU, sizeof(ev), &ev); +} + +static void notify_server_mtu(struct app_connection *conn) +{ + struct hal_ev_gatt_server_mtu_changed ev; + size_t mtu; + + g_attrib_get_buffer(conn->device->attrib, &mtu); + + ev.conn_id = conn->id; + ev.mtu = mtu; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_MTU_CHANGED, sizeof(ev), &ev); +} + +static void notify_mtu_change(void *data, void *user_data) +{ + struct gatt_device *device = user_data; + struct app_connection *conn = data; + + if (conn->device != device) + return; + + if (!conn->app) { + error("gatt: can't notify mtu - no app registered for conn"); + return; + } + + switch (conn->app->type) { + case GATT_CLIENT: + notify_client_mtu_change(conn, true); + break; + case GATT_SERVER: + notify_server_mtu(conn); + break; + default: + break; + } +} + +static bool update_mtu(struct gatt_device *device, uint16_t rmtu) +{ + uint16_t mtu, lmtu; + + if (!get_local_mtu(device, &lmtu)) + return false; + + DBG("remote_mtu:%d local_mtu:%d", rmtu, lmtu); + + if (rmtu < ATT_DEFAULT_LE_MTU) { + error("gatt: remote MTU invalid (%u bytes)", rmtu); + return false; + } + + mtu = MIN(lmtu, rmtu); + + if (mtu == ATT_DEFAULT_LE_MTU) + return true; + + if (!g_attrib_set_mtu(device->attrib, mtu)) { + error("gatt: Failed to set MTU"); + return false; + } + + queue_foreach(app_connections, notify_mtu_change, device); + + return true; +} + +static void att_handler(const uint8_t *ipdu, uint16_t len, gpointer user_data); + +static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct gatt_device *device = user_data; + uint16_t rmtu; + + DBG(""); + + if (status) { + error("gatt: MTU exchange: %s", att_ecode2str(status)); + goto failed; + } + + if (!dec_mtu_resp(pdu, plen, &rmtu)) { + error("gatt: MTU exchange: protocol error"); + goto failed; + } + + update_mtu(device, rmtu); + +failed: + device_unref(device); +} + +static void send_exchange_mtu_request(struct gatt_device *device) +{ + uint16_t mtu; + + if (!get_local_mtu(device, &mtu)) + return; + + DBG("mtu %u", mtu); + + if (!gatt_exchange_mtu(device->attrib, mtu, exchange_mtu_cb, + device_ref(device))) + device_unref(device); +} + +static void ignore_confirmation_cb(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + /* Ignored. */ +} + +static void notify_att_range_change(struct gatt_device *dev, + struct att_range *range) +{ + uint16_t handle; + uint16_t length = 0; + uint16_t ccc; + uint8_t *pdu; + size_t mtu; + GAttribResultFunc confirmation_cb = NULL; + + handle = gatt_db_attribute_get_handle(service_changed_attrib); + if (!handle) + return; + + ccc = bt_get_gatt_ccc(&dev->bdaddr); + if (!ccc) + return; + + pdu = g_attrib_get_buffer(dev->attrib, &mtu); + + switch (ccc) { + case 0x0001: + length = enc_notification(handle, (uint8_t *) range, + sizeof(*range), pdu, mtu); + break; + case 0x0002: + length = enc_indication(handle, (uint8_t *) range, + sizeof(*range), pdu, mtu); + confirmation_cb = ignore_confirmation_cb; + break; + default: + /* 0xfff4 reserved for future use */ + break; + } + + g_attrib_send(dev->attrib, 0, pdu, length, confirmation_cb, NULL, NULL); +} + +static struct app_connection *create_connection(struct gatt_device *device, + struct gatt_app *app) +{ + struct app_connection *new_conn; + static int32_t last_conn_id = 1; + + /* Check if already connected */ + new_conn = new0(struct app_connection, 1); + + /* Make connection id unique to connection record (app, device) pair */ + new_conn->app = app; + new_conn->id = last_conn_id++; + new_conn->transactions = queue_new(); + + queue_push_head(app_connections, new_conn); + + new_conn->device = device_ref(device); + + return new_conn; +} + +static struct service *create_service(uint8_t id, bool primary, char *uuid, + void *data) +{ + struct service *s; + + s = new0(struct service, 1); + + if (bt_string_to_uuid(&s->id.uuid, uuid) < 0) { + error("gatt: Cannot convert string to uuid"); + free(s); + return NULL; + } + + s->chars = queue_new(); + s->included = queue_new(); + s->id.instance = id; + + /* Put primary service to our local list */ + s->primary = primary; + if (s->primary) + memcpy(&s->prim, data, sizeof(s->prim)); + else + memcpy(&s->incl, data, sizeof(s->incl)); + + return s; +} + +static void send_client_primary_notify(void *data, void *user_data) +{ + struct hal_ev_gatt_client_search_result ev; + struct service *p = data; + int32_t conn_id = PTR_TO_INT(user_data); + + /* In service queue we will have also included services */ + if (!p->primary) + return; + + ev.conn_id = conn_id; + element_id_to_hal_srvc_id(&p->id, 1, &ev.srvc_id); + + uuid2android(&p->id.uuid, ev.srvc_id.uuid); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_SEARCH_RESULT, sizeof(ev), &ev); +} + +static void send_client_search_complete_notify(int32_t status, int32_t conn_id) +{ + struct hal_ev_gatt_client_search_complete ev; + + ev.status = status; + ev.conn_id = conn_id; + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_SEARCH_COMPLETE, sizeof(ev), &ev); +} + +struct discover_srvc_data { + bt_uuid_t uuid; + struct app_connection *conn; +}; + +static void discover_srvc_by_uuid_cb(uint8_t status, GSList *ranges, + void *user_data) +{ + struct discover_srvc_data *cb_data = user_data; + struct gatt_primary prim; + struct service *s; + int32_t gatt_status; + struct gatt_device *dev = cb_data->conn->device; + uint8_t instance_id = queue_length(dev->services); + + DBG("Status %d", status); + + if (status) { + error("gatt: Discover pri srvc filtered by uuid failed: %s", + att_ecode2str(status)); + gatt_status = GATT_FAILURE; + goto reply; + } + + if (!ranges) { + info("gatt: No primary services searched by uuid found"); + gatt_status = GATT_SUCCESS; + goto reply; + } + + bt_uuid_to_string(&cb_data->uuid, prim.uuid, sizeof(prim.uuid)); + + for (; ranges; ranges = ranges->next) { + memcpy(&prim.range, ranges->data, sizeof(prim.range)); + + s = create_service(instance_id++, true, prim.uuid, &prim); + if (!s) { + gatt_status = GATT_FAILURE; + goto reply; + } + + queue_push_tail(dev->services, s); + + send_client_primary_notify(s, INT_TO_PTR(cb_data->conn->id)); + + DBG("attr handle = 0x%04x, end grp handle = 0x%04x uuid: %s", + prim.range.start, prim.range.end, prim.uuid); + } + + /* Partial search service scanning was performed */ + dev->partial_srvc_search = true; + gatt_status = GATT_SUCCESS; + +reply: + send_client_search_complete_notify(gatt_status, cb_data->conn->id); + free(cb_data); +} + +static void discover_srvc_all_cb(uint8_t status, GSList *services, + void *user_data) +{ + struct discover_srvc_data *cb_data = user_data; + struct gatt_device *dev = cb_data->conn->device; + int32_t gatt_status; + GSList *l; + /* + * There might be multiply services with same uuid. Therefore make sure + * each primary service one has unique instance_id + */ + uint8_t instance_id = queue_length(dev->services); + + DBG("Status %d", status); + + if (status) { + error("gatt: Discover all primary services failed: %s", + att_ecode2str(status)); + gatt_status = GATT_FAILURE; + goto reply; + } + + if (!services) { + info("gatt: No primary services found"); + gatt_status = GATT_SUCCESS; + goto reply; + } + + for (l = services; l; l = l->next) { + struct gatt_primary *prim = l->data; + struct service *p; + + if (queue_find(dev->services, match_srvc_by_range, + &prim->range)) + continue; + + p = create_service(instance_id++, true, prim->uuid, prim); + if (!p) + continue; + + queue_push_tail(dev->services, p); + + DBG("attr handle = 0x%04x, end grp handle = 0x%04x uuid: %s", + prim->range.start, prim->range.end, prim->uuid); + } + + /* + * Send all found services notifications - first cache, + * then send notifies + */ + queue_foreach(dev->services, send_client_primary_notify, + INT_TO_PTR(cb_data->conn->id)); + + /* Full search service scanning was performed */ + dev->partial_srvc_search = false; + gatt_status = GATT_SUCCESS; + +reply: + send_client_search_complete_notify(gatt_status, cb_data->conn->id); + free(cb_data); +} + +static gboolean connection_timeout(void *user_data) +{ + struct app_connection *conn = user_data; + + conn->timeout_id = 0; + + queue_remove(app_connections, conn); + destroy_connection(conn); + + return FALSE; +} + +static void discover_primary_cb(uint8_t status, GSList *services, + void *user_data) +{ + struct discover_srvc_data *cb_data = user_data; + struct app_connection *conn = cb_data->conn; + struct gatt_device *dev = conn->device; + GSList *l, *uuids = NULL; + + DBG("Status %d", status); + + if (status) { + error("gatt: Discover all primary services failed: %s", + att_ecode2str(status)); + free(cb_data); + + return; + } + + if (!services) { + info("gatt: No primary services found"); + free(cb_data); + + return; + } + + for (l = services; l; l = l->next) { + struct gatt_primary *prim = l->data; + uint8_t *new_uuid; + bt_uuid_t uuid, u128; + + DBG("uuid: %s", prim->uuid); + + if (bt_string_to_uuid(&uuid, prim->uuid) < 0) { + error("gatt: Cannot convert string to uuid"); + continue; + } + + bt_uuid_to_uuid128(&uuid, &u128); + new_uuid = g_memdup(&u128.value.u128, sizeof(u128.value.u128)); + + uuids = g_slist_prepend(uuids, new_uuid); + } + + bt_device_set_uuids(&dev->bdaddr, uuids); + + free(cb_data); + + conn->timeout_id = g_timeout_add_seconds(GATT_CONN_TIMEOUT, + connection_timeout, conn); +} + +static guint search_dev_for_srvc(struct app_connection *conn, bt_uuid_t *uuid) +{ + struct discover_srvc_data *cb_data; + + cb_data = new0(struct discover_srvc_data, 1); + cb_data->conn = conn; + + if (uuid) { + memcpy(&cb_data->uuid, uuid, sizeof(cb_data->uuid)); + return gatt_discover_primary(conn->device->attrib, uuid, + discover_srvc_by_uuid_cb, cb_data); + } + + if (conn->app) + return gatt_discover_primary(conn->device->attrib, NULL, + discover_srvc_all_cb, cb_data); + + return gatt_discover_primary(conn->device->attrib, NULL, + discover_primary_cb, cb_data); +} + +struct connect_data { + struct gatt_device *dev; + int32_t status; +}; + +static void notify_app_connect_status_by_device(void *data, void *user_data) +{ + struct app_connection *conn = data; + struct connect_data *con_data = user_data; + + if (conn->device == con_data->dev) + notify_app_connect_status(conn, con_data->status); +} + +static struct app_connection *find_conn_without_app(struct gatt_device *dev) +{ + struct app_connection conn_match; + + conn_match.device = dev; + conn_match.app = NULL; + + return queue_find(app_connections, match_connection_by_device_and_app, + &conn_match); +} + +static struct app_connection *find_conn(const bdaddr_t *addr, int32_t app_id) +{ + struct app_connection conn_match; + struct gatt_device *dev; + struct gatt_app *app; + + /* Check if app is registered */ + app = find_app_by_id(app_id); + if (!app) { + error("gatt: Client id %d not found", app_id); + return NULL; + } + + /* Check if device is known */ + dev = find_device_by_addr(addr); + if (!dev) { + error("gatt: Client id %d not found", app_id); + return NULL; + } + + conn_match.device = dev; + conn_match.app = app; + + return queue_find(app_connections, match_connection_by_device_and_app, + &conn_match); +} + +static void create_app_connection(void *data, void *user_data) +{ + struct gatt_device *dev = user_data; + struct gatt_app *app; + + app = find_app_by_id(PTR_TO_INT(data)); + if (!app) + return; + + DBG("Autoconnect application id=%d", app->id); + + if (!find_conn(&dev->bdaddr, PTR_TO_INT(data))) + create_connection(dev, app); +} + +static void ind_handler(const uint8_t *cmd, uint16_t cmd_len, + gpointer user_data) +{ + struct gatt_device *dev = user_data; + uint16_t resp_length = 0; + size_t length; + uint8_t *opdu = g_attrib_get_buffer(dev->attrib, &length); + + /* + * We have to send confirmation here. If some client is + * registered for this indication, event will be send in + * handle_notification + */ + + resp_length = enc_confirmation(opdu, length); + g_attrib_send(dev->attrib, 0, opdu, resp_length, NULL, NULL, NULL); +} + +static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + struct gatt_device *dev = user_data; + struct connect_data data; + struct att_range range; + uint32_t status; + GError *err = NULL; + GAttrib *attrib; + uint16_t mtu, cid; + + if (dev->state != DEVICE_CONNECT_READY) { + error("gatt: Device not in a connecting state!?"); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + if (dev->att_io) { + g_io_channel_unref(dev->att_io); + dev->att_io = NULL; + } + + if (gerr) { + error("gatt: connection failed %s", gerr->message); + device_set_state(dev, DEVICE_DISCONNECTED); + status = GATT_FAILURE; + goto reply; + } + + if (!bt_io_get(io, &err, BT_IO_OPT_IMTU, &mtu, BT_IO_OPT_CID, &cid, + BT_IO_OPT_INVALID)) { + error("gatt: Could not get imtu or cid: %s", err->message); + device_set_state(dev, DEVICE_DISCONNECTED); + status = GATT_FAILURE; + g_error_free(err); + goto reply; + } + + /* on BR/EDR MTU must not be less then minimal allowed MTU */ + if (cid != ATT_CID && mtu < ATT_DEFAULT_L2CAP_MTU) { + error("gatt: MTU too small (%u bytes)", mtu); + device_set_state(dev, DEVICE_DISCONNECTED); + status = GATT_FAILURE; + goto reply; + } + + DBG("mtu %u cid %u", mtu, cid); + + /* on LE we always start with default MTU */ + if (cid == ATT_CID) + mtu = ATT_DEFAULT_LE_MTU; + + attrib = g_attrib_new(io, mtu, true); + if (!attrib) { + error("gatt: unable to create new GAttrib instance"); + device_set_state(dev, DEVICE_DISCONNECTED); + status = GATT_FAILURE; + goto reply; + } + + dev->attrib = attrib; + dev->watch_id = g_io_add_watch(io, G_IO_HUP | G_IO_ERR | G_IO_NVAL, + disconnected_cb, dev); + + dev->server_id = g_attrib_register(attrib, GATTRIB_ALL_REQS, + GATTRIB_ALL_HANDLES, + att_handler, dev, NULL); + dev->ind_id = g_attrib_register(attrib, ATT_OP_HANDLE_IND, + GATTRIB_ALL_HANDLES, + ind_handler, dev, NULL); + if ((dev->server_id && dev->ind_id) == 0) + error("gatt: Could not attach to server"); + + device_set_state(dev, DEVICE_CONNECTED); + + /* Send exchange mtu request as we assume being client and server */ + /* TODO: Dont exchange mtu if no client apps */ + + /* MTU exchange shall not be used on BR/EDR - Vol 3. Part G. 4.3.1 */ + if (cid == ATT_CID) + send_exchange_mtu_request(dev); + + /* + * Service Changed Characteristic and CCC Descriptor handles + * should not change if there are bonded devices. We have them + * constant all the time, thus they should be excluded from + * range indicating changes. + */ + range.start = gatt_db_attribute_get_handle(service_changed_attrib) + 2; + range.end = 0xffff; + + /* + * If there is ccc stored for that device we were acting as server for + * it, and as we dont have last connect and last services (de)activation + * timestamps we should always assume something has changed. + */ + notify_att_range_change(dev, &range); + + status = GATT_SUCCESS; + +reply: + /* + * Make sure there are app_connections for all apps interested in auto + * connect to that device + */ + queue_foreach(dev->autoconnect_apps, create_app_connection, dev); + + if (!queue_find(app_connections, match_connection_by_device, dev)) { + struct app_connection *conn; + + if (!dev->attrib) + return; + + conn = create_connection(dev, NULL); + if (!conn) + return; + + if (bt_is_pairing(&dev->bdaddr)) + /* + * If there is bonding ongoing lets wait for paired + * callback. Once we get that we can start search + * services + */ + conn->timeout_id = g_timeout_add_seconds( + GATT_PAIR_CONN_TIMEOUT, + connection_timeout, conn); + else + /* + * There is no ongoing bonding, lets search for primary + * services + */ + search_dev_for_srvc(conn, NULL); + } + + data.dev = dev; + data.status = status; + queue_foreach(app_connections, notify_app_connect_status_by_device, + &data); + + /* For BR/EDR notify about MTU since it is not negotiable*/ + if (cid != ATT_CID && status == GATT_SUCCESS) + queue_foreach(app_connections, notify_mtu_change, dev); + + device_unref(dev); + + /* Check if we should restart scan */ + if (scanning) + bt_le_discovery_start(); + + /* FIXME: What to do if discovery won't start here. */ +} + +static int connect_le(struct gatt_device *dev) +{ + GIOChannel *io; + GError *gerr = NULL; + char addr[18]; + const bdaddr_t *bdaddr; + uint8_t bdaddr_type; + + ba2str(&dev->bdaddr, addr); + + /* There is one connection attempt going on */ + if (dev->att_io) { + info("gatt: connection to dev %s is ongoing", addr); + return -EALREADY; + } + + DBG("Connection attempt to: %s", addr); + + bdaddr = bt_get_id_addr(&dev->bdaddr, &bdaddr_type); + + /* + * This connection will help us catch any PDUs that comes before + * pairing finishes + */ + io = bt_io_connect(connect_cb, device_ref(dev), NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, + BT_IO_OPT_DEST_BDADDR, bdaddr, + BT_IO_OPT_DEST_TYPE, bdaddr_type, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!io) { + error("gatt: Failed bt_io_connect(%s): %s", addr, + gerr->message); + g_error_free(gerr); + return -EIO; + } + + /* Keep this, so we can cancel the connection */ + dev->att_io = io; + + device_set_state(dev, DEVICE_CONNECT_READY); + + return 0; +} + +static int connect_next_dev(void) +{ + struct gatt_device *dev; + + DBG(""); + + dev = find_device_by_state(DEVICE_CONNECT_READY); + if (!dev) + return -ENODEV; + + return connect_le(dev); +} + +static void bt_le_discovery_stop_cb(void) +{ + DBG(""); + + /* Check now if there is any device ready to connect */ + if (connect_next_dev() < 0) + bt_le_discovery_start(); +} + +static void le_device_found_handler(const bdaddr_t *addr, int rssi, + uint16_t eir_len, const void *eir, + bool connectable, bool bonded) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_client_scan_result *ev = (void *) buf; + struct gatt_device *dev; + char bda[18]; + + if (!scanning) + goto done; + + ba2str(addr, bda); + DBG("LE Device found: %s, rssi: %d, adv_data: %d", bda, rssi, !!eir); + + bdaddr2android(addr, ev->bda); + ev->rssi = rssi; + ev->len = eir_len; + + memcpy(ev->adv_data, eir, ev->len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_SCAN_RESULT, + sizeof(*ev) + ev->len, ev); + +done: + if (!connectable) + return; + + /* We use auto connect feature from kernel if possible */ + if (bt_kernel_conn_control()) + return; + + dev = find_device_by_addr(addr); + if (!dev) { + if (!bonded) + return; + + dev = create_device(addr); + } + + if (dev->state != DEVICE_CONNECT_INIT) + return; + + device_set_state(dev, DEVICE_CONNECT_READY); + + /* + * We are ok to perform connect now. Stop discovery + * and once it is stopped continue with creating ACL + */ + bt_le_discovery_stop(bt_le_discovery_stop_cb); +} + +static struct gatt_app *register_app(const uint8_t *uuid, gatt_type_t type) +{ + static int32_t application_id = 1; + struct gatt_app *app; + + if (queue_find(gatt_apps, match_app_by_uuid, uuid)) { + error("gatt: app uuid is already on list"); + return NULL; + } + + app = new0(struct gatt_app, 1); + + app->type = type; + + if (app->type == GATT_CLIENT) + app->notifications = queue_new(); + + memcpy(app->uuid, uuid, sizeof(app->uuid)); + + app->id = application_id++; + + queue_push_head(gatt_apps, app); + + if (app->type == GATT_SERVER) + queue_push_tail(listen_apps, INT_TO_PTR(app->id)); + + return app; +} + +static void handle_client_register(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_register *cmd = buf; + struct hal_ev_gatt_client_register_client ev; + struct gatt_app *app; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + app = register_app(cmd->uuid, GATT_CLIENT); + + if (app) { + ev.client_if = app->id; + ev.status = GATT_SUCCESS; + } else { + ev.status = GATT_FAILURE; + } + + /* We should send notification with given in cmd UUID */ + memcpy(ev.app_uuid, cmd->uuid, sizeof(ev.app_uuid)); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_REGISTER_CLIENT, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_REGISTER, + HAL_STATUS_SUCCESS); +} + +static void handle_client_scan(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_scan *cmd = buf; + uint8_t status; + + DBG("new state %d", cmd->start); + + if (cmd->client_if != 0) { + void *registered = find_app_by_id(cmd->client_if); + + if (!registered) { + error("gatt: Client not registered"); + status = HAL_STATUS_FAILED; + goto reply; + } + } + + /* Turn off scan */ + if (!cmd->start) { + DBG("Stopping LE SCAN"); + + if (scanning) { + bt_le_discovery_stop(NULL); + scanning = false; + } + + status = HAL_STATUS_SUCCESS; + goto reply; + } + + /* Reply success if we already do scan */ + if (scanning) { + status = HAL_STATUS_SUCCESS; + goto reply; + } + + /* Turn on scan */ + if (!bt_le_discovery_start()) { + error("gatt: LE scan switch failed"); + status = HAL_STATUS_FAILED; + goto reply; + } + + scanning = true; + status = HAL_STATUS_SUCCESS; + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_SCAN, + status); +} + +static int connect_bredr(struct gatt_device *dev) +{ + BtIOSecLevel sec_level; + GIOChannel *io; + GError *gerr = NULL; + char addr[18]; + + ba2str(&dev->bdaddr, addr); + + /* There is one connection attempt going on */ + if (dev->att_io) { + info("gatt: connection to dev %s is ongoing", addr); + return -EALREADY; + } + + DBG("Connection attempt to: %s", addr); + + sec_level = bt_device_is_bonded(&dev->bdaddr) ? BT_IO_SEC_MEDIUM : + BT_IO_SEC_LOW; + + io = bt_io_connect(connect_cb, device_ref(dev), NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_SOURCE_TYPE, BDADDR_BREDR, + BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, + BT_IO_OPT_DEST_TYPE, BDADDR_BREDR, + BT_IO_OPT_PSM, ATT_PSM, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + if (!io) { + error("gatt: Failed bt_io_connect(%s): %s", addr, + gerr->message); + g_error_free(gerr); + return -EIO; + } + + device_set_state(dev, DEVICE_CONNECT_READY); + + /* Keep this, so we can cancel the connection */ + dev->att_io = io; + + return 0; +} + +static bool trigger_connection(struct app_connection *conn, bool direct) +{ + switch (conn->device->state) { + case DEVICE_DISCONNECTED: + /* + * If device was last seen over BR/EDR connect over it. + * Note: Connection state is handled in connect_bredr() func + */ + if (bt_device_last_seen_bearer(&conn->device->bdaddr) == + BDADDR_BREDR) + return connect_bredr(conn->device) == 0; + + if (direct) + return connect_le(conn->device) == 0; + + bt_gatt_add_autoconnect(conn->app->id, &conn->device->bdaddr); + return auto_connect_le(conn->device); + case DEVICE_CONNECTED: + notify_app_connect_status(conn, GATT_SUCCESS); + return true; + case DEVICE_CONNECT_READY: + case DEVICE_CONNECT_INIT: + default: + /* In those cases connection is already triggered. */ + return true; + } +} + +static void remove_autoconnect_device(struct gatt_device *dev) +{ + bt_auto_connect_remove(&dev->bdaddr); + + if (dev->state == DEVICE_CONNECT_INIT) + device_set_state(dev, DEVICE_DISCONNECTED); + + device_unref(dev); +} + +static void clear_autoconnect_devices(void *data, void *user_data) +{ + struct gatt_device *dev = data; + + if (queue_remove(dev->autoconnect_apps, user_data)) + if (queue_isempty(dev->autoconnect_apps)) + remove_autoconnect_device(dev); +} + +static uint8_t unregister_app(int client_if) +{ + struct gatt_app *cl; + + /* + * Make sure that there is no devices in auto connect list for this + * application + */ + queue_foreach(gatt_devices, clear_autoconnect_devices, + INT_TO_PTR(client_if)); + + cl = queue_remove_if(gatt_apps, match_app_by_id, INT_TO_PTR(client_if)); + if (!cl) { + error("gatt: client_if=%d not found", client_if); + + return HAL_STATUS_FAILED; + } + + /* Destroy app connections with proper notifications for this app. */ + queue_remove_all(app_connections, match_connection_by_app, cl, + destroy_connection); + destroy_gatt_app(cl); + + return HAL_STATUS_SUCCESS; +} + +static void send_client_listen_notify(int32_t id, int32_t status) +{ + struct hal_ev_gatt_client_listen ev; + + /* Server if because of typo in android headers */ + ev.server_if = id; + ev.status = status; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, HAL_EV_GATT_CLIENT_LISTEN, + sizeof(ev), &ev); +} + +struct listen_data { + int32_t client_id; + bool start; +}; + +static struct listen_data *create_listen_data(int32_t client_id, bool start) +{ + struct listen_data *d; + + d = new0(struct listen_data, 1); + d->client_id = client_id; + d->start = start; + + return d; +} + +static void set_advertising_cb(uint8_t status, void *user_data) +{ + struct listen_data *l = user_data; + + send_client_listen_notify(l->client_id, status); + + /* In case of success update advertising state*/ + if (!status) + advertising_cnt = l->start ? 1 : 0; + + /* + * Let's remove client from the list in two cases + * 1. Start failed + * 2. Stop succeed + */ + if ((l->start && status) || (!l->start && !status)) + queue_remove(listen_apps, INT_TO_PTR(l->client_id)); + + free(l); +} + +static void handle_client_unregister(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_unregister *cmd = buf; + uint8_t status; + void *listening_client; + struct listen_data *data; + + DBG(""); + + listening_client = queue_find(listen_apps, NULL, + INT_TO_PTR(cmd->client_if)); + + if (listening_client) { + advertising_cnt--; + queue_remove(listen_apps, INT_TO_PTR(cmd->client_if)); + } else { + status = unregister_app(cmd->client_if); + goto reply; + } + + if (!advertising_cnt) { + data = create_listen_data(cmd->client_if, false); + + if (!bt_le_set_advertising(data->start, set_advertising_cb, + data)) { + error("gatt: Could not set advertising"); + status = HAL_STATUS_FAILED; + free(data); + goto reply; + } + } + + status = unregister_app(cmd->client_if); + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_UNREGISTER, status); +} + +static uint8_t handle_connect(int32_t app_id, const bdaddr_t *addr, bool direct) +{ + struct app_connection conn_match; + struct app_connection *conn; + struct gatt_device *device; + struct gatt_app *app; + + DBG(""); + + app = find_app_by_id(app_id); + if (!app) + return HAL_STATUS_FAILED; + + device = find_device_by_addr(addr); + if (!device) + device = create_device(addr); + + conn_match.device = device; + conn_match.app = app; + + conn = queue_find(app_connections, match_connection_by_device_and_app, + &conn_match); + if (!conn) { + conn = create_connection(device, app); + if (!conn) + return HAL_STATUS_NOMEM; + } + + if (!trigger_connection(conn, direct)) + return HAL_STATUS_FAILED; + + return HAL_STATUS_SUCCESS; +} + +static void handle_client_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_connect *cmd = buf; + uint8_t status; + bdaddr_t addr; + + DBG("is_direct:%u transport:%u", cmd->is_direct, cmd->transport); + + android2bdaddr(&cmd->bdaddr, &addr); + + /* TODO handle transport flag */ + + status = handle_connect(cmd->client_if, &addr, cmd->is_direct); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_CONNECT, + status); +} + +static void handle_client_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_disconnect *cmd = buf; + struct app_connection *conn; + uint8_t status; + + DBG(""); + + /* TODO: should we care to match also bdaddr when conn_id is unique? */ + conn = queue_remove_if(app_connections, match_connection_by_id, + INT_TO_PTR(cmd->conn_id)); + destroy_connection(conn); + + status = HAL_STATUS_SUCCESS; + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DISCONNECT, status); +} + +static void handle_client_listen(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_listen *cmd = buf; + uint8_t status; + struct listen_data *data; + bool req_sent = false; + void *listening_client; + + DBG(""); + + if (!find_app_by_id(cmd->client_if)) { + error("gatt: Client not registered"); + status = HAL_STATUS_FAILED; + goto reply; + } + + listening_client = queue_find(listen_apps, NULL, + INT_TO_PTR(cmd->client_if)); + /* Start listening */ + if (cmd->start) { + if (listening_client) { + status = HAL_STATUS_SUCCESS; + goto reply; + } + + queue_push_tail(listen_apps, INT_TO_PTR(cmd->client_if)); + + /* If listen is already on just return success*/ + if (advertising_cnt > 0) { + advertising_cnt++; + status = HAL_STATUS_SUCCESS; + goto reply; + } + } else { + /* Stop listening. Check if client was listening */ + if (!listening_client) { + error("gatt: This client %d does not listen", + cmd->client_if); + status = HAL_STATUS_FAILED; + goto reply; + } + + /* + * In case there is more listening clients don't stop + * advertising + */ + if (advertising_cnt > 1) { + advertising_cnt--; + queue_remove(listen_apps, INT_TO_PTR(cmd->client_if)); + status = HAL_STATUS_SUCCESS; + goto reply; + } + } + + data = create_listen_data(cmd->client_if, cmd->start); + + if (!bt_le_set_advertising(cmd->start, set_advertising_cb, data)) { + error("gatt: Could not set advertising"); + status = HAL_STATUS_FAILED; + free(data); + goto reply; + } + + /* + * Use this flag to keep in mind that we are waiting for callback with + * result + */ + req_sent = true; + + status = HAL_STATUS_SUCCESS; + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_LISTEN, + status); + + /* In case of early success or error, just send notification up */ + if (!req_sent) { + int32_t gatt_status = status == HAL_STATUS_SUCCESS ? + GATT_SUCCESS : GATT_FAILURE; + send_client_listen_notify(cmd->client_if, gatt_status); + } +} + +static void handle_client_refresh(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_refresh *cmd = buf; + struct gatt_device *dev; + uint8_t status; + bdaddr_t bda; + + /* + * This is Android's framework hidden API call. It seams that no + * notification is expected and Bluedroid silently updates device's + * cache under the hood. As we use lazy caching ,we can just clear the + * cache and we're done. + */ + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &bda); + dev = find_device_by_addr(&bda); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + queue_remove_all(dev->services, NULL, NULL, destroy_service); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_REFRESH, + status); +} + +static void handle_client_search_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_search_service *cmd = buf; + struct app_connection *conn; + uint8_t status; + struct service *s; + bt_uuid_t uuid; + guint srvc_search_success; + + DBG(""); + + if (len != sizeof(*cmd) + (cmd->filtered ? 16 : 0)) { + error("Invalid search service size (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + conn = find_connection_by_id(cmd->conn_id); + if (!conn) { + error("gatt: dev with conn_id=%d not found", cmd->conn_id); + + status = HAL_STATUS_FAILED; + goto reply; + } + + if (conn->device->state != DEVICE_CONNECTED) { + char bda[18]; + + ba2str(&conn->device->bdaddr, bda); + error("gatt: device %s not connected", bda); + + status = HAL_STATUS_FAILED; + goto reply; + } + + if (cmd->filtered) + android2uuid(cmd->filter_uuid, &uuid); + + /* Services not cached yet */ + if (queue_isempty(conn->device->services)) { + if (cmd->filtered) + srvc_search_success = search_dev_for_srvc(conn, &uuid); + else + srvc_search_success = search_dev_for_srvc(conn, NULL); + + if (!srvc_search_success) { + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; + goto reply; + } + + /* Search in cached services for given service */ + if (cmd->filtered) { + /* Search in cache for service by uuid */ + s = queue_find(conn->device->services, match_srvc_by_bt_uuid, + &uuid); + + if (s) { + send_client_primary_notify(s, INT_TO_PTR(conn->id)); + } else { + if (!search_dev_for_srvc(conn, &uuid)) { + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; + goto reply; + } + } else { + /* Refresh service cache if only partial search was performed */ + if (conn->device->partial_srvc_search) { + srvc_search_success = search_dev_for_srvc(conn, NULL); + if (!srvc_search_success) { + status = HAL_STATUS_FAILED; + goto reply; + } + } else + queue_foreach(conn->device->services, + send_client_primary_notify, + INT_TO_PTR(cmd->conn_id)); + } + + send_client_search_complete_notify(GATT_SUCCESS, conn->id); + + status = HAL_STATUS_SUCCESS; + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SEARCH_SERVICE, status); +} + +static void send_client_incl_service_notify(const struct element_id *srvc_id, + const struct service *incl, + int32_t conn_id) +{ + struct hal_ev_gatt_client_get_inc_service ev; + + memset(&ev, 0, sizeof(ev)); + + ev.conn_id = conn_id; + + element_id_to_hal_srvc_id(srvc_id, 1, &ev.srvc_id); + + if (incl) { + element_id_to_hal_srvc_id(&incl->id, 0, &ev.incl_srvc_id); + ev.status = GATT_SUCCESS; + } else { + ev.status = GATT_FAILURE; + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT , + HAL_EV_GATT_CLIENT_GET_INC_SERVICE, + sizeof(ev), &ev); +} + +struct get_included_data { + struct service *prim; + struct app_connection *conn; +}; + +static int get_inst_id_of_prim_services(const struct gatt_device *dev) +{ + struct service *s = queue_peek_tail(dev->services); + + if (s) + return s->id.instance; + + return -1; +} + +static void get_included_cb(uint8_t status, GSList *included, void *user_data) +{ + struct get_included_data *data = user_data; + struct app_connection *conn = data->conn; + struct service *service = data->prim; + struct service *incl = NULL; + int instance_id; + + DBG(""); + + free(data); + + if (status) { + error("gatt: no included services found"); + goto failed; + } + + /* Remember that we already search included services.*/ + service->incl_search_done = true; + + /* + * There might be multiply services with same uuid. Therefore make sure + * each service has unique instance id. Let's take the latest instance + * id of primary service and start iterate included services from this + * point. + */ + instance_id = get_inst_id_of_prim_services(conn->device); + if (instance_id < 0) + goto failed; + + for (; included; included = included->next) { + struct gatt_included *included_service = included->data; + + incl = create_service(++instance_id, false, + included_service->uuid, + included_service); + if (!incl) + continue; + + /* + * Lets keep included service on two queues. + * 1. on services queue together with primary service + * 2. on special queue inside primary service + */ + queue_push_tail(service->included, incl); + queue_push_tail(conn->device->services, incl); + } + + /* + * Notify upper layer about first included service. + * Android framework will iterate for next one. + */ + incl = queue_peek_head(service->included); + +failed: + send_client_incl_service_notify(&service->id, incl, conn->id); +} + +static void search_included_services(struct app_connection *conn, + struct service *service) +{ + struct get_included_data *data; + uint16_t start, end; + + data = new0(struct get_included_data, 1); + data->prim = service; + data->conn = conn; + + if (service->primary) { + start = service->prim.range.start; + end = service->prim.range.end; + } else { + start = service->incl.range.start; + end = service->incl.range.end; + } + + gatt_find_included(conn->device->attrib, start, end, get_included_cb, + data); +} + +static bool find_service(int32_t conn_id, struct element_id *service_id, + struct app_connection **connection, + struct service **service) +{ + struct service *srvc; + struct app_connection *conn; + + conn = find_connection_by_id(conn_id); + if (!conn) { + error("gatt: conn_id=%d not found", conn_id); + return false; + } + + srvc = queue_find(conn->device->services, match_srvc_by_element_id, + service_id); + if (!srvc) { + error("gatt: Service with inst_id: %d not found", + service_id->instance); + return false; + } + + *connection = conn; + *service = srvc; + + return true; +} + +static void handle_client_get_included_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_get_included_service *cmd = buf; + struct app_connection *conn; + struct service *prim_service; + struct service *incl_service = NULL; + struct element_id match_id; + struct element_id srvc_id; + uint8_t status; + + DBG(""); + + hal_srvc_id_to_element_id(&cmd->srvc_id, &srvc_id); + + if (len != sizeof(*cmd) + + (cmd->continuation ? sizeof(cmd->incl_srvc_id[0]) : 0)) { + error("Invalid get incl services size (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + hal_srvc_id_to_element_id(&cmd->srvc_id, &match_id); + if (!find_service(cmd->conn_id, &match_id, &conn, &prim_service)) { + status = HAL_STATUS_FAILED; + goto notify; + } + + if (!prim_service->incl_search_done) { + search_included_services(conn, prim_service); + status = HAL_STATUS_SUCCESS; + goto reply; + } + + /* Try to use cache here */ + if (!cmd->continuation) { + incl_service = queue_peek_head(prim_service->included); + } else { + uint8_t inst_id = cmd->incl_srvc_id[0].inst_id; + + incl_service = queue_find(prim_service->included, + match_srvc_by_higher_inst_id, + INT_TO_PTR(inst_id)); + } + + status = HAL_STATUS_SUCCESS; + +notify: + /* + * In case of error in handling request we need to send event with + * service id of cmd and gatt failure status. + */ + send_client_incl_service_notify(&srvc_id, incl_service, cmd->conn_id); + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_INCLUDED_SERVICE, status); +} + +static void send_client_char_notify(const struct hal_gatt_srvc_id *service, + const struct hal_gatt_gatt_id *charac, + int32_t char_prop, int32_t conn_id) +{ + struct hal_ev_gatt_client_get_characteristic ev; + + ev.conn_id = conn_id; + + if (charac) { + memcpy(&ev.char_id, charac, sizeof(struct hal_gatt_gatt_id)); + ev.char_prop = char_prop; + ev.status = GATT_SUCCESS; + } else { + memset(&ev.char_id, 0, sizeof(struct hal_gatt_gatt_id)); + ev.char_prop = 0; + ev.status = GATT_FAILURE; + } + + memcpy(&ev.srvc_id, service, sizeof(struct hal_gatt_srvc_id)); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_GET_CHARACTERISTIC, + sizeof(ev), &ev); +} + +static void convert_send_client_char_notify(const struct characteristic *ch, + int32_t conn_id, + const struct service *service) +{ + struct hal_gatt_srvc_id srvc; + struct hal_gatt_gatt_id charac; + + element_id_to_hal_srvc_id(&service->id, service->primary, &srvc); + + if (ch) { + element_id_to_hal_gatt_id(&ch->id, &charac); + send_client_char_notify(&srvc, &charac, ch->ch.properties, + conn_id); + } else { + send_client_char_notify(&srvc, NULL, 0, conn_id); + } +} + +static void cache_all_srvc_chars(struct service *srvc, GSList *characteristics) +{ + uint16_t inst_id = 0; + bt_uuid_t uuid; + + for (; characteristics; characteristics = characteristics->next) { + struct characteristic *ch; + + ch = new0(struct characteristic, 1); + ch->descriptors = queue_new(); + + memcpy(&ch->ch, characteristics->data, sizeof(ch->ch)); + + bt_string_to_uuid(&uuid, ch->ch.uuid); + bt_uuid_to_uuid128(&uuid, &ch->id.uuid); + + /* + * For now we increment inst_id and use it as characteristic + * handle + */ + ch->id.instance = ++inst_id; + + /* Store end handle to use later for descriptors discovery */ + if (characteristics->next) { + struct gatt_char *next = characteristics->next->data; + + ch->end_handle = next->handle - 1; + } else { + ch->end_handle = srvc->primary ? srvc->prim.range.end : + srvc->incl.range.end; + } + + DBG("attr handle = 0x%04x, end handle = 0x%04x uuid: %s", + ch->ch.handle, ch->end_handle, ch->ch.uuid); + + queue_push_tail(srvc->chars, ch); + } +} + +struct discover_char_data { + int32_t conn_id; + struct service *service; +}; + +static void discover_char_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + struct discover_char_data *data = user_data; + struct service *srvc = data->service; + + if (status) { + error("gatt: Failed to get characteristics: %s", + att_ecode2str(status)); + convert_send_client_char_notify(NULL, data->conn_id, srvc); + goto done; + } + + if (queue_isempty(srvc->chars)) + cache_all_srvc_chars(srvc, characteristics); + + convert_send_client_char_notify(queue_peek_head(srvc->chars), + data->conn_id, srvc); + +done: + free(data); +} + +static void handle_client_get_characteristic(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_get_characteristic *cmd = buf; + struct characteristic *ch; + struct element_id match_id; + struct app_connection *conn; + struct service *srvc; + uint8_t status; + + DBG(""); + + if (len != sizeof(*cmd) + (cmd->continuation ? sizeof(cmd->char_id[0]) : 0)) { + error("Invalid get characteristic size (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + hal_srvc_id_to_element_id(&cmd->srvc_id, &match_id); + if (!find_service(cmd->conn_id, &match_id, &conn, &srvc)) { + status = HAL_STATUS_FAILED; + goto done; + } + + /* Discover all characteristics for services if not cached yet */ + if (queue_isempty(srvc->chars)) { + struct discover_char_data *cb_data; + struct att_range range; + + cb_data = new0(struct discover_char_data, 1); + cb_data->service = srvc; + cb_data->conn_id = conn->id; + + range = srvc->primary ? srvc->prim.range : srvc->incl.range; + + if (!gatt_discover_char(conn->device->attrib, range.start, + range.end, NULL, + discover_char_cb, cb_data)) { + free(cb_data); + + status = HAL_STATUS_FAILED; + goto done; + } + + status = HAL_STATUS_SUCCESS; + goto done; + } + + if (cmd->continuation) + ch = queue_find(srvc->chars, match_char_by_higher_inst_id, + INT_TO_PTR(cmd->char_id[0].inst_id)); + else + ch = queue_peek_head(srvc->chars); + + convert_send_client_char_notify(ch, conn->id, srvc); + + status = HAL_STATUS_SUCCESS; + +done: + if (status != HAL_STATUS_SUCCESS) + send_client_char_notify(&cmd->srvc_id, NULL, 0, cmd->conn_id); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_CHARACTERISTIC, status); +} + +static void send_client_descr_notify(int32_t status, int32_t conn_id, + bool primary, + const struct element_id *srvc, + const struct element_id *ch, + const struct element_id *opt_descr) +{ + struct hal_ev_gatt_client_get_descriptor ev; + + memset(&ev, 0, sizeof(ev)); + + ev.status = status; + ev.conn_id = conn_id; + + element_id_to_hal_srvc_id(srvc, primary, &ev.srvc_id); + element_id_to_hal_gatt_id(ch, &ev.char_id); + + if (opt_descr) + element_id_to_hal_gatt_id(opt_descr, &ev.descr_id); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_GET_DESCRIPTOR, sizeof(ev), &ev); +} + +struct discover_desc_data { + struct app_connection *conn; + struct service *srvc; + struct characteristic *ch; +}; + +static void gatt_discover_desc_cb(guint8 status, GSList *descs, + gpointer user_data) +{ + struct discover_desc_data *data = user_data; + struct app_connection *conn = data->conn; + struct service *srvc = data->srvc; + struct characteristic *ch = data->ch; + struct descriptor *descr; + int i = 0; + + if (status != 0) { + error("Discover all characteristic descriptors failed [%s]: %s", + ch->ch.uuid, att_ecode2str(status)); + goto reply; + } + + for ( ; descs; descs = descs->next) { + struct gatt_desc *desc = descs->data; + bt_uuid_t uuid; + + descr = new0(struct descriptor, 1); + + bt_string_to_uuid(&uuid, desc->uuid); + bt_uuid_to_uuid128(&uuid, &descr->id.uuid); + + descr->id.instance = ++i; + descr->handle = desc->handle; + + DBG("attr handle = 0x%04x, uuid: %s", desc->handle, desc->uuid); + + queue_push_tail(ch->descriptors, descr); + } + +reply: + descr = queue_peek_head(ch->descriptors); + + send_client_descr_notify(status ? GATT_FAILURE : GATT_SUCCESS, conn->id, + srvc->primary, &srvc->id, &ch->id, + descr ? &descr->id : NULL); + + free(data); +} + +static bool build_descr_cache(struct app_connection *conn, struct service *srvc, + struct characteristic *ch) +{ + struct discover_desc_data *cb_data; + uint16_t start, end; + + /* Clip range to given characteristic */ + start = ch->ch.value_handle + 1; + end = ch->end_handle; + + /* If there are no descriptors, notify with fail status. */ + if (start > end) + return false; + + cb_data = new0(struct discover_desc_data, 1); + cb_data->conn = conn; + cb_data->srvc = srvc; + cb_data->ch = ch; + + if (!gatt_discover_desc(conn->device->attrib, start, end, NULL, + gatt_discover_desc_cb, cb_data)) { + free(cb_data); + return false; + } + + return true; +} + +static void handle_client_get_descriptor(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_get_descriptor *cmd = buf; + struct descriptor *descr = NULL; + struct characteristic *ch; + struct service *srvc; + struct element_id srvc_id; + struct element_id char_id; + struct app_connection *conn; + int32_t conn_id; + uint8_t primary; + uint8_t status; + + DBG(""); + + if (len != sizeof(*cmd) + + (cmd->continuation ? sizeof(cmd->descr_id[0]) : 0)) { + error("gatt: Invalid get descr command (%u bytes), terminating", + len); + + raise(SIGTERM); + return; + } + + conn_id = cmd->conn_id; + primary = cmd->srvc_id.is_primary; + + hal_srvc_id_to_element_id(&cmd->srvc_id, &srvc_id); + hal_gatt_id_to_element_id(&cmd->char_id, &char_id); + + if (!find_service(conn_id, &srvc_id, &conn, &srvc)) { + error("gatt: Get descr. could not find service"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + ch = queue_find(srvc->chars, match_char_by_element_id, &char_id); + if (!ch) { + error("gatt: Get descr. could not find characteristic"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + if (queue_isempty(ch->descriptors)) { + if (build_descr_cache(conn, srvc, ch)) { + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_DESCRIPTOR, + HAL_STATUS_SUCCESS); + return; + } + } + + status = HAL_STATUS_SUCCESS; + + /* Send from cache */ + if (cmd->continuation) + descr = queue_find(ch->descriptors, + match_descr_by_higher_inst_id, + INT_TO_PTR(cmd->descr_id[0].inst_id)); + else + descr = queue_peek_head(ch->descriptors); + +failed: + send_client_descr_notify(descr ? GATT_SUCCESS : GATT_FAILURE, conn_id, + primary, &srvc_id, &char_id, + &descr->id); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_DESCRIPTOR, status); +} + +struct char_op_data { + int32_t conn_id; + const struct element_id *srvc_id; + const struct element_id *char_id; + uint8_t primary; +}; + +static struct char_op_data *create_char_op_data(int32_t conn_id, + const struct element_id *s_id, + const struct element_id *ch_id, + bool primary) +{ + struct char_op_data *d; + + d = new0(struct char_op_data, 1); + d->conn_id = conn_id; + d->srvc_id = s_id; + d->char_id = ch_id; + d->primary = primary; + + return d; +} + +static void send_client_read_char_notify(int32_t status, const uint8_t *pdu, + uint16_t len, int32_t conn_id, + const struct element_id *s_id, + const struct element_id *ch_id, + uint8_t primary) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_client_read_characteristic *ev = (void *) buf; + ssize_t vlen; + + memset(buf, 0, sizeof(buf)); + + ev->conn_id = conn_id; + ev->status = status; + ev->data.status = status; + + element_id_to_hal_srvc_id(s_id, primary, &ev->data.srvc_id); + element_id_to_hal_gatt_id(ch_id, &ev->data.char_id); + + if (status == 0 && pdu) { + vlen = dec_read_resp(pdu, len, ev->data.value, sizeof(buf)); + if (vlen < 0) { + error("gatt: Protocol error"); + ev->status = GATT_FAILURE; + } else { + ev->data.len = vlen; + } + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_READ_CHARACTERISTIC, + sizeof(*ev) + ev->data.len, ev); +} + +static void read_char_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct char_op_data *data = user_data; + + send_client_read_char_notify(status, pdu, len, data->conn_id, + data->srvc_id, data->char_id, + data->primary); + + free(data); +} + +static int get_cid(struct gatt_device *dev) +{ + GIOChannel *io; + uint16_t cid; + + io = g_attrib_get_channel(dev->attrib); + + if (!bt_io_get(io, NULL, BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID)) { + error("gatt: Failed to get CID"); + return -1; + } + + return cid; +} + +static int get_sec_level(struct gatt_device *dev) +{ + GIOChannel *io; + int sec_level; + + io = g_attrib_get_channel(dev->attrib); + + if (!bt_io_get(io, NULL, BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_INVALID)) { + error("gatt: Failed to get sec_level"); + return -1; + } + + return sec_level; +} + +static bool set_security(struct gatt_device *device, int req_sec_level) +{ + int sec_level; + GError *gerr = NULL; + GIOChannel *io; + + sec_level = get_sec_level(device); + if (sec_level < 0) + return false; + + if (req_sec_level <= sec_level) + return true; + + io = g_attrib_get_channel(device->attrib); + if (!io) + return false; + + bt_io_set(io, &gerr, BT_IO_OPT_SEC_LEVEL, req_sec_level, + BT_IO_OPT_INVALID); + if (gerr) { + error("gatt: Failed to set security level: %s", gerr->message); + g_error_free(gerr); + return false; + } + + return true; +} + +bool bt_gatt_set_security(const bdaddr_t *bdaddr, int sec_level) +{ + struct gatt_device *device; + + device = find_device_by_addr(bdaddr); + if (!device) + return false; + + return set_security(device, sec_level); +} + +static bool set_auth_type(struct gatt_device *device, int auth_type) +{ + int sec_level; + + switch (auth_type) { + case HAL_GATT_AUTHENTICATION_MITM: + sec_level = BT_SECURITY_HIGH; + break; + case HAL_GATT_AUTHENTICATION_NO_MITM: + sec_level = BT_SECURITY_MEDIUM; + break; + case HAL_GATT_AUTHENTICATION_NONE: + sec_level = BT_SECURITY_LOW; + break; + default: + error("gatt: Invalid auth_type value: %d", auth_type); + return false; + } + + return set_security(device, sec_level); +} + +static void handle_client_read_characteristic(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_read_characteristic *cmd = buf; + struct char_op_data *cb_data; + struct characteristic *ch; + struct app_connection *conn; + struct service *srvc; + struct element_id srvc_id; + struct element_id char_id; + uint8_t status; + + DBG(""); + + /* TODO authorization needs to be handled */ + + hal_srvc_id_to_element_id(&cmd->srvc_id, &srvc_id); + hal_gatt_id_to_element_id(&cmd->char_id, &char_id); + + if (!find_service(cmd->conn_id, &srvc_id, &conn, &srvc)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + /* search characteristics by element id */ + ch = queue_find(srvc->chars, match_char_by_element_id, &char_id); + if (!ch) { + error("gatt: Characteristic with inst_id: %d not found", + cmd->char_id.inst_id); + status = HAL_STATUS_FAILED; + goto failed; + } + + cb_data = create_char_op_data(cmd->conn_id, &srvc->id, &ch->id, + cmd->srvc_id.is_primary); + + if (!set_auth_type(conn->device, cmd->auth_req)) { + error("gatt: Failed to set security %d", cmd->auth_req); + status = HAL_STATUS_FAILED; + free(cb_data); + goto failed; + } + + if (!gatt_read_char(conn->device->attrib, ch->ch.value_handle, + read_char_cb, cb_data)) { + error("gatt: Cannot read characteristic with inst_id: %d", + cmd->char_id.inst_id); + status = HAL_STATUS_FAILED; + free(cb_data); + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_CHARACTERISTIC, status); + + /* + * We should send notification with service, characteristic id in case + * of errors. + */ + if (status != HAL_STATUS_SUCCESS) + send_client_read_char_notify(GATT_FAILURE, NULL, 0, + cmd->conn_id, &srvc_id, + &char_id, + cmd->srvc_id.is_primary); +} + +static void send_client_write_char_notify(int32_t status, int32_t conn_id, + const struct element_id *srvc_id, + const struct element_id *char_id, + uint8_t primary) +{ + struct hal_ev_gatt_client_write_characteristic ev; + + memset(&ev, 0, sizeof(ev)); + + ev.conn_id = conn_id; + ev.status = status; + ev.data.status = status; + + element_id_to_hal_srvc_id(srvc_id, primary, &ev.data.srvc_id); + element_id_to_hal_gatt_id(char_id, &ev.data.char_id); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_WRITE_CHARACTERISTIC, + sizeof(ev), &ev); +} + +static void write_char_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct char_op_data *data = user_data; + + send_client_write_char_notify(status, data->conn_id, data->srvc_id, + data->char_id, data->primary); + + free(data); +} + +static guint signed_write_cmd(struct gatt_device *dev, uint16_t handle, + const uint8_t *value, uint16_t vlen) +{ + uint8_t csrk[16]; + uint32_t sign_cnt; + guint res; + + memset(csrk, 0, 16); + + if (!bt_get_csrk(&dev->bdaddr, true, csrk, &sign_cnt, NULL)) { + error("gatt: Could not get csrk key"); + return 0; + } + + res = gatt_signed_write_cmd(dev->attrib, handle, value, vlen, crypto, + csrk, sign_cnt, NULL, NULL); + if (!res) { + error("gatt: Signed write command failed"); + return 0; + } + + bt_update_sign_counter(&dev->bdaddr, true, ++sign_cnt); + + return res; +} + +static void handle_client_write_characteristic(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_write_characteristic *cmd = buf; + struct char_op_data *cb_data = NULL; + struct characteristic *ch; + struct app_connection *conn; + struct service *srvc; + struct element_id srvc_id; + struct element_id char_id; + uint8_t status; + guint res; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid write char size (%u bytes), terminating", len); + raise(SIGTERM); + return; + } + + hal_srvc_id_to_element_id(&cmd->srvc_id, &srvc_id); + hal_gatt_id_to_element_id(&cmd->char_id, &char_id); + + if (!find_service(cmd->conn_id, &srvc_id, &conn, &srvc)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + /* search characteristics by instance id */ + ch = queue_find(srvc->chars, match_char_by_element_id, &char_id); + if (!ch) { + error("gatt: Characteristic with inst_id: %d not found", + cmd->char_id.inst_id); + status = HAL_STATUS_FAILED; + goto failed; + } + + if (cmd->write_type == GATT_WRITE_TYPE_PREPARE || + cmd->write_type == GATT_WRITE_TYPE_DEFAULT) { + cb_data = create_char_op_data(cmd->conn_id, &srvc->id, &ch->id, + cmd->srvc_id.is_primary); + } + + if (!set_auth_type(conn->device, cmd->auth_req)) { + error("gatt: Failed to set security %d", cmd->auth_req); + status = HAL_STATUS_FAILED; + goto failed; + } + + switch (cmd->write_type) { + case GATT_WRITE_TYPE_NO_RESPONSE: + res = gatt_write_cmd(conn->device->attrib, ch->ch.value_handle, + cmd->value, cmd->len, + NULL, NULL); + break; + case GATT_WRITE_TYPE_PREPARE: + res = gatt_reliable_write_char(conn->device->attrib, + ch->ch.value_handle, + cmd->value, cmd->len, + write_char_cb, cb_data); + break; + case GATT_WRITE_TYPE_DEFAULT: + res = gatt_write_char(conn->device->attrib, ch->ch.value_handle, + cmd->value, cmd->len, + write_char_cb, cb_data); + break; + case GATT_WRITE_TYPE_SIGNED: + if (get_cid(conn->device) != ATT_CID) { + error("gatt: Cannot write signed on BR/EDR bearer"); + status = HAL_STATUS_FAILED; + goto failed; + } + + if (get_sec_level(conn->device) > BT_SECURITY_LOW) + res = gatt_write_cmd(conn->device->attrib, + ch->ch.value_handle, cmd->value, + cmd->len, NULL, NULL); + else + res = signed_write_cmd(conn->device, + ch->ch.value_handle, cmd->value, + cmd->len); + break; + default: + error("gatt: Write type %d unsupported", cmd->write_type); + status = HAL_STATUS_UNSUPPORTED; + goto failed; + } + + if (!res) { + error("gatt: Cannot write char. with inst_id: %d", + cmd->char_id.inst_id); + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_WRITE_CHARACTERISTIC, status); + + /* + * We should send notification with service, characteristic id in case + * of error and write with no response + */ + if (status != HAL_STATUS_SUCCESS || + cmd->write_type == GATT_WRITE_TYPE_NO_RESPONSE || + cmd->write_type == GATT_WRITE_TYPE_SIGNED) { + int32_t gatt_status = (status == HAL_STATUS_SUCCESS) ? + GATT_SUCCESS : GATT_FAILURE; + + send_client_write_char_notify(gatt_status, cmd->conn_id, + &srvc_id, &char_id, + cmd->srvc_id.is_primary); + free(cb_data); + } +} + +static void send_client_descr_read_notify(int32_t status, const uint8_t *pdu, + guint16 len, int32_t conn_id, + const struct element_id *srvc, + const struct element_id *ch, + const struct element_id *descr, + uint8_t primary) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_client_read_descriptor *ev = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + ev->status = status; + ev->conn_id = conn_id; + ev->data.status = ev->status; + + element_id_to_hal_srvc_id(srvc, primary, &ev->data.srvc_id); + element_id_to_hal_gatt_id(ch, &ev->data.char_id); + element_id_to_hal_gatt_id(descr, &ev->data.descr_id); + + if (status == 0 && pdu) { + ssize_t ret; + + ret = dec_read_resp(pdu, len, ev->data.value, + GATT_MAX_ATTR_LEN); + if (ret < 0) { + error("gatt: Protocol error"); + ev->status = GATT_FAILURE; + } else { + ev->data.len = ret; + } + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_READ_DESCRIPTOR, + sizeof(*ev) + ev->data.len, ev); +} + +struct desc_data { + int32_t conn_id; + const struct element_id *srvc_id; + const struct element_id *char_id; + const struct element_id *descr_id; + uint8_t primary; +}; + +static void read_desc_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct desc_data *cb_data = user_data; + + if (status != 0) + error("gatt: Discover all char descriptors failed: %s", + att_ecode2str(status)); + + send_client_descr_read_notify(status, pdu, len, cb_data->conn_id, + cb_data->srvc_id, cb_data->char_id, + cb_data->descr_id, cb_data->primary); + + free(cb_data); +} + +static struct desc_data *create_desc_data(int32_t conn_id, + const struct element_id *s_id, + const struct element_id *ch_id, + const struct element_id *d_id, + uint8_t primary) +{ + struct desc_data *d; + + d = new0(struct desc_data, 1); + d->conn_id = conn_id; + d->srvc_id = s_id; + d->char_id = ch_id; + d->descr_id = d_id; + d->primary = primary; + + return d; +} + +static void handle_client_read_descriptor(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_read_descriptor *cmd = buf; + struct desc_data *cb_data; + struct characteristic *ch; + struct descriptor *descr; + struct service *srvc; + struct element_id char_id; + struct element_id descr_id; + struct element_id srvc_id; + struct app_connection *conn; + int32_t conn_id = 0; + uint8_t primary; + uint8_t status; + + DBG(""); + + conn_id = cmd->conn_id; + primary = cmd->srvc_id.is_primary; + + hal_srvc_id_to_element_id(&cmd->srvc_id, &srvc_id); + hal_gatt_id_to_element_id(&cmd->char_id, &char_id); + hal_gatt_id_to_element_id(&cmd->descr_id, &descr_id); + + if (!find_service(conn_id, &srvc_id, &conn, &srvc)) { + error("gatt: Read descr. could not find service"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + ch = queue_find(srvc->chars, match_char_by_element_id, &char_id); + if (!ch) { + error("gatt: Read descr. could not find characteristic"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + descr = queue_find(ch->descriptors, match_descr_by_element_id, + &descr_id); + if (!descr) { + error("gatt: Read descr. could not find descriptor"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + cb_data = create_desc_data(conn_id, &srvc->id, &ch->id, &descr->id, + primary); + + if (!set_auth_type(conn->device, cmd->auth_req)) { + error("gatt: Failed to set security %d", cmd->auth_req); + status = HAL_STATUS_FAILED; + free(cb_data); + goto failed; + } + + if (!gatt_read_char(conn->device->attrib, descr->handle, read_desc_cb, + cb_data)) { + free(cb_data); + + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + if (status != HAL_STATUS_SUCCESS) + send_client_descr_read_notify(GATT_FAILURE, NULL, 0, conn_id, + &srvc_id, &char_id, &descr_id, + primary); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_DESCRIPTOR, status); +} + +static void send_client_descr_write_notify(int32_t status, int32_t conn_id, + const struct element_id *srvc, + const struct element_id *ch, + const struct element_id *descr, + uint8_t primary) { + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_client_write_descriptor *ev = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + ev->status = status; + ev->conn_id = conn_id; + + element_id_to_hal_srvc_id(srvc, primary, &ev->data.srvc_id); + element_id_to_hal_gatt_id(ch, &ev->data.char_id); + element_id_to_hal_gatt_id(descr, &ev->data.descr_id); + ev->data.status = status; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_WRITE_DESCRIPTOR, + sizeof(*ev), ev); +} + +static void write_descr_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct desc_data *cb_data = user_data; + + if (status) + error("gatt: Write descriptors failed: %s", + att_ecode2str(status)); + + send_client_descr_write_notify(status, cb_data->conn_id, + cb_data->srvc_id, cb_data->char_id, + cb_data->descr_id, cb_data->primary); + + free(cb_data); +} + +static void handle_client_write_descriptor(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_write_descriptor *cmd = buf; + struct desc_data *cb_data = NULL; + struct characteristic *ch; + struct descriptor *descr; + struct service *srvc; + struct element_id srvc_id; + struct element_id char_id; + struct element_id descr_id; + struct app_connection *conn; + int32_t conn_id; + uint8_t primary; + uint8_t status; + guint res; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid write desriptor command (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + primary = cmd->srvc_id.is_primary; + conn_id = cmd->conn_id; + + hal_srvc_id_to_element_id(&cmd->srvc_id, &srvc_id); + hal_gatt_id_to_element_id(&cmd->char_id, &char_id); + hal_gatt_id_to_element_id(&cmd->descr_id, &descr_id); + + if (!find_service(cmd->conn_id, &srvc_id, &conn, &srvc)) { + error("gatt: Write descr. could not find service"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + ch = queue_find(srvc->chars, match_char_by_element_id, &char_id); + if (!ch) { + error("gatt: Write descr. could not find characteristic"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + descr = queue_find(ch->descriptors, match_descr_by_element_id, + &descr_id); + if (!descr) { + error("gatt: Write descr. could not find descriptor"); + + status = HAL_STATUS_FAILED; + goto failed; + } + + if (cmd->write_type != GATT_WRITE_TYPE_NO_RESPONSE) + cb_data = create_desc_data(conn_id, &srvc->id, &ch->id, + &descr->id, primary); + + if (!set_auth_type(conn->device, cmd->auth_req)) { + error("gatt: Failed to set security %d", cmd->auth_req); + status = HAL_STATUS_FAILED; + goto failed; + } + + switch (cmd->write_type) { + case GATT_WRITE_TYPE_NO_RESPONSE: + res = gatt_write_cmd(conn->device->attrib, descr->handle, + cmd->value, cmd->len, NULL , NULL); + break; + case GATT_WRITE_TYPE_PREPARE: + res = gatt_reliable_write_char(conn->device->attrib, + descr->handle, cmd->value, + cmd->len, write_descr_cb, + cb_data); + break; + case GATT_WRITE_TYPE_DEFAULT: + res = gatt_write_char(conn->device->attrib, descr->handle, + cmd->value, cmd->len, + write_descr_cb, cb_data); + break; + default: + error("gatt: Write type %d unsupported", cmd->write_type); + status = HAL_STATUS_UNSUPPORTED; + goto failed; + } + + if (!res) { + error("gatt: Write desc, could not write desc"); + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + if (status != HAL_STATUS_SUCCESS || + cmd->write_type == GATT_WRITE_TYPE_NO_RESPONSE) { + int32_t gatt_status = (status == HAL_STATUS_SUCCESS) ? + GATT_SUCCESS : GATT_FAILURE; + + send_client_descr_write_notify(gatt_status, conn_id, &srvc_id, + &char_id, &descr_id, primary); + free(cb_data); + } + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_WRITE_DESCRIPTOR, status); +} + +static void send_client_write_execute_notify(int32_t id, int32_t status) +{ + struct hal_ev_gatt_client_exec_write ev; + + ev.conn_id = id; + ev.status = status; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_EXEC_WRITE, + sizeof(ev), &ev); +} + +static void write_execute_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + send_client_write_execute_notify(PTR_TO_INT(user_data), status); +} + +static void handle_client_execute_write(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_execute_write *cmd = buf; + struct app_connection *conn; + uint8_t status; + uint8_t flags; + + DBG(""); + + conn = find_connection_by_id(cmd->conn_id); + if (!conn) { + status = HAL_STATUS_FAILED; + goto reply; + } + + flags = cmd->execute ? ATT_WRITE_ALL_PREP_WRITES : + ATT_CANCEL_ALL_PREP_WRITES; + + if (!gatt_execute_write(conn->device->attrib, flags, write_execute_cb, + INT_TO_PTR(cmd->conn_id))) { + error("gatt: Could not send execute write"); + status = HAL_STATUS_FAILED; + goto reply; + } + + status = HAL_STATUS_SUCCESS; +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_EXECUTE_WRITE, status); + + /* In case of early error send also notification.*/ + if (status != HAL_STATUS_SUCCESS) + send_client_write_execute_notify(cmd->conn_id, GATT_FAILURE); +} + +static void handle_notification(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_client_notify *ev = (void *) buf; + struct notification_data *notification = user_data; + uint8_t data_offset = sizeof(uint8_t) + sizeof(uint16_t); + + if (len < data_offset) + return; + + memcpy(&ev->char_id, ¬ification->ch, sizeof(ev->char_id)); + memcpy(&ev->srvc_id, ¬ification->service, sizeof(ev->srvc_id)); + bdaddr2android(¬ification->conn->device->bdaddr, &ev->bda); + ev->conn_id = notification->conn->id; + ev->is_notify = pdu[0] == ATT_OP_HANDLE_NOTIFY; + + /* We have to cut opcode and handle from data */ + ev->len = len - data_offset; + memcpy(ev->value, pdu + data_offset, len - data_offset); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, HAL_EV_GATT_CLIENT_NOTIFY, + sizeof(*ev) + ev->len, ev); +} + +static void send_register_for_notification_ev(int32_t id, int32_t registered, + int32_t status, + const struct hal_gatt_srvc_id *srvc, + const struct hal_gatt_gatt_id *ch) +{ + struct hal_ev_gatt_client_reg_for_notif ev; + + ev.conn_id = id; + ev.status = status; + ev.registered = registered; + memcpy(&ev.srvc_id, srvc, sizeof(ev.srvc_id)); + memcpy(&ev.char_id, ch, sizeof(ev.char_id)); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_REGISTER_FOR_NOTIF, sizeof(ev), &ev); +} + +static void handle_client_register_for_notification(const void *buf, + uint16_t len) +{ + const struct hal_cmd_gatt_client_register_for_notification *cmd = buf; + struct notification_data *notification; + struct characteristic *c; + struct element_id match_id; + struct app_connection *conn; + int32_t conn_id = 0; + struct service *service; + uint8_t status; + int32_t gatt_status; + bdaddr_t addr; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &addr); + + conn = find_conn(&addr, cmd->client_if); + if (!conn) { + status = HAL_STATUS_FAILED; + goto failed; + } + + conn_id = conn->id; + + hal_srvc_id_to_element_id(&cmd->srvc_id, &match_id); + service = queue_find(conn->device->services, match_srvc_by_element_id, + &match_id); + if (!service) { + status = HAL_STATUS_FAILED; + goto failed; + } + + hal_gatt_id_to_element_id(&cmd->char_id, &match_id); + c = queue_find(service->chars, match_char_by_element_id, &match_id); + if (!c) { + status = HAL_STATUS_FAILED; + goto failed; + } + + notification = new0(struct notification_data, 1); + + memcpy(¬ification->ch, &cmd->char_id, sizeof(notification->ch)); + memcpy(¬ification->service, &cmd->srvc_id, + sizeof(notification->service)); + notification->conn = conn; + + if (queue_find(conn->app->notifications, match_notification, + notification)) { + free(notification); + status = HAL_STATUS_SUCCESS; + goto failed; + } + + notification->notif_id = g_attrib_register(conn->device->attrib, + ATT_OP_HANDLE_NOTIFY, + c->ch.value_handle, + handle_notification, + notification, + destroy_notification); + if (!notification->notif_id) { + free(notification); + status = HAL_STATUS_FAILED; + goto failed; + } + + notification->ind_id = g_attrib_register(conn->device->attrib, + ATT_OP_HANDLE_IND, + c->ch.value_handle, + handle_notification, + notification, + destroy_notification); + if (!notification->ind_id) { + g_attrib_unregister(conn->device->attrib, + notification->notif_id); + free(notification); + status = HAL_STATUS_FAILED; + goto failed; + } + + /* + * Because same data - notification - is shared by two handlers, we + * introduce ref counter to be sure that data can be freed with no risk. + * Counter is decremented in destroy_notification. + */ + notification->ref = 2; + + queue_push_tail(conn->app->notifications, notification); + + status = HAL_STATUS_SUCCESS; + +failed: + gatt_status = status ? GATT_FAILURE : GATT_SUCCESS; + send_register_for_notification_ev(conn_id, 1, gatt_status, + &cmd->srvc_id, &cmd->char_id); + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_REGISTER_FOR_NOTIFICATION, status); +} + +static void handle_client_deregister_for_notification(const void *buf, + uint16_t len) +{ + const struct hal_cmd_gatt_client_deregister_for_notification *cmd = buf; + struct notification_data *notification, notif; + struct app_connection *conn; + int32_t conn_id = 0; + uint8_t status; + int32_t gatt_status; + bdaddr_t addr; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &addr); + + conn = find_conn(&addr, cmd->client_if); + if (!conn) { + status = HAL_STATUS_FAILED; + goto failed; + } + + conn_id = conn->id; + + memcpy(¬if.ch, &cmd->char_id, sizeof(notif.ch)); + memcpy(¬if.service, &cmd->srvc_id, sizeof(notif.service)); + notif.conn = conn; + + notification = queue_find(conn->app->notifications, + match_notification, ¬if); + if (!notification) { + status = HAL_STATUS_FAILED; + goto failed; + } + + unregister_notification(notification); + + status = HAL_STATUS_SUCCESS; + +failed: + gatt_status = status ? GATT_FAILURE : GATT_SUCCESS; + send_register_for_notification_ev(conn_id, 0, gatt_status, + &cmd->srvc_id, &cmd->char_id); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DEREGISTER_FOR_NOTIFICATION, status); +} + +static void send_client_remote_rssi_notify(int32_t client_if, + const bdaddr_t *addr, + int32_t rssi, int32_t status) +{ + struct hal_ev_gatt_client_read_remote_rssi ev; + + ev.client_if = client_if; + bdaddr2android(addr, &ev.address); + ev.rssi = rssi; + ev.status = status; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_READ_REMOTE_RSSI, sizeof(ev), &ev); +} + +static void read_remote_rssi_cb(uint8_t status, const bdaddr_t *addr, + int8_t rssi, void *user_data) +{ + int32_t client_if = PTR_TO_INT(user_data); + int32_t gatt_status = status ? GATT_FAILURE : GATT_SUCCESS; + + send_client_remote_rssi_notify(client_if, addr, rssi, gatt_status); +} + +static void handle_client_read_remote_rssi(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_read_remote_rssi *cmd = buf; + uint8_t status; + bdaddr_t bdaddr; + + DBG(""); + + if (!find_app_by_id(cmd->client_if)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + android2bdaddr(cmd->bdaddr, &bdaddr); + if (!bt_read_device_rssi(&bdaddr, read_remote_rssi_cb, + INT_TO_PTR(cmd->client_if))) { + error("gatt: Could not read RSSI"); + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_REMOTE_RSSI, status); + + if (status != HAL_STATUS_SUCCESS) + send_client_remote_rssi_notify(cmd->client_if, &bdaddr, 0, + GATT_FAILURE); +} + +static void handle_client_get_device_type(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_get_device_type *cmd = buf; + struct hal_rsp_gatt_client_get_device_type rsp; + bdaddr_t bdaddr; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + rsp.type = bt_get_device_android_type(&bdaddr); + + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_DEVICE_TYPE, + sizeof(rsp), &rsp, -1); +} + +static void handle_client_set_adv_data(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_set_adv_data *cmd = buf; + uint8_t status; + + if (len != sizeof(*cmd) + cmd->manufacturer_len) { + error("Invalid set adv data command (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + DBG("scan_rsp=%u name=%u tx=%u min=%d max=%d app=%d", + cmd->set_scan_rsp, cmd->include_name, cmd->include_txpower, + cmd->min_interval, cmd->max_interval, cmd->appearance); + + DBG("manufacturer=%u service_data=%u service_uuid=%u", + cmd->manufacturer_len, cmd->service_data_len, + cmd->service_uuid_len); + + /* TODO This should be implemented when kernel supports it */ + if (cmd->manufacturer_len || cmd->service_data_len || + cmd->service_uuid_len) { + error("gatt: Extra advertising data not supported"); + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SET_ADV_DATA, status); +} + +static void test_command_result(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + DBG("status: %d", status); +} + +static uint8_t test_read_write(bdaddr_t *bdaddr, bt_uuid_t *uuid, uint16_t op, + uint16_t u2, uint16_t u3, + uint16_t u4, uint16_t u5) +{ + guint16 length = 0; + struct gatt_device *dev; + uint8_t *pdu; + size_t mtu; + + dev = find_device_by_addr(bdaddr); + if (!dev || dev->state != DEVICE_CONNECTED) + return HAL_STATUS_FAILED; + + pdu = g_attrib_get_buffer(dev->attrib, &mtu); + if (!pdu) + return HAL_STATUS_FAILED; + + switch (op) { + case ATT_OP_READ_REQ: + length = enc_read_req(u2, pdu, mtu); + break; + case ATT_OP_READ_BY_TYPE_REQ: + length = enc_read_by_type_req(u2, u3, uuid, pdu, mtu); + break; + case ATT_OP_READ_BLOB_REQ: + length = enc_read_blob_req(u2, u3, pdu, mtu); + break; + case ATT_OP_READ_BY_GROUP_REQ: + length = enc_read_by_grp_req(u2, u3, uuid, pdu, mtu); + break; + case ATT_OP_READ_MULTI_REQ: + return HAL_STATUS_UNSUPPORTED; + case ATT_OP_WRITE_REQ: + length = enc_write_req(u2, (uint8_t *) &u3, sizeof(u3), pdu, + mtu); + break; + case ATT_OP_WRITE_CMD: + length = enc_write_cmd(u2, (uint8_t *) &u3, sizeof(u3), pdu, + mtu); + break; + case ATT_OP_PREP_WRITE_REQ: + length = enc_prep_write_req(u2, u3, (uint8_t *) &u4, sizeof(u4), + pdu, mtu); + break; + case ATT_OP_EXEC_WRITE_REQ: + length = enc_exec_write_req(u2, pdu, mtu); + break; + case ATT_OP_SIGNED_WRITE_CMD: + if (signed_write_cmd(dev, u2, (uint8_t *) &u3, sizeof(u3))) + return HAL_STATUS_SUCCESS; + else + return HAL_STATUS_FAILED; + default: + error("gatt: Unknown operation type"); + + return HAL_STATUS_UNSUPPORTED; + } + + if (!g_attrib_send(dev->attrib, 0, pdu, length, test_command_result, + NULL, NULL)) + return HAL_STATUS_FAILED; + + return HAL_STATUS_SUCCESS; +} + +static uint8_t test_increase_security(bdaddr_t *bdaddr, uint16_t u1) +{ + struct gatt_device *device; + + device = find_device_by_addr(bdaddr); + if (!device) + return HAL_STATUS_FAILED; + + if (!set_auth_type(device, u1)) + return HAL_STATUS_FAILED; + + return HAL_STATUS_SUCCESS; +} + +static void handle_client_test_command(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_test_command *cmd = buf; + struct gatt_app *app; + bdaddr_t bdaddr; + bt_uuid_t uuid; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bda1, &bdaddr); + android2uuid(cmd->uuid1, &uuid); + + switch (cmd->command) { + case GATT_CLIENT_TEST_CMD_ENABLE: + if (cmd->u1) { + if (!test_client_if) { + app = register_app(TEST_UUID, GATT_CLIENT); + if (app) + test_client_if = app->id; + } + + if (test_client_if) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + } else { + status = unregister_app(test_client_if); + test_client_if = 0; + } + break; + case GATT_CLIENT_TEST_CMD_CONNECT: + /* TODO u1 holds device type, for now assume BLE */ + status = handle_connect(test_client_if, &bdaddr, false); + break; + case GATT_CLIENT_TEST_CMD_DISCONNECT: + app = queue_find(gatt_apps, match_app_by_id, + INT_TO_PTR(test_client_if)); + queue_remove_all(app_connections, match_connection_by_app, app, + destroy_connection); + + status = HAL_STATUS_SUCCESS; + break; + case GATT_CLIENT_TEST_CMD_DISCOVER: + status = HAL_STATUS_FAILED; + break; + case GATT_CLIENT_TEST_CMD_READ: + case GATT_CLIENT_TEST_CMD_WRITE: + status = test_read_write(&bdaddr, &uuid, cmd->u1, cmd->u2, + cmd->u3, cmd->u4, cmd->u5); + break; + case GATT_CLIENT_TEST_CMD_INCREASE_SECURITY: + status = test_increase_security(&bdaddr, cmd->u1); + break; + case GATT_CLIENT_TEST_CMD_PAIRING_CONFIG: + default: + status = HAL_STATUS_FAILED; + break; + } + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_TEST_COMMAND, status); +} + +static void handle_server_register(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_register *cmd = buf; + struct hal_ev_gatt_server_register ev; + struct gatt_app *app; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + app = register_app(cmd->uuid, GATT_SERVER); + + if (app) { + ev.server_if = app->id; + ev.status = GATT_SUCCESS; + } else { + ev.status = GATT_FAILURE; + } + + memcpy(ev.uuid, cmd->uuid, sizeof(ev.uuid)); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_REGISTER, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_REGISTER, + HAL_STATUS_SUCCESS); +} + +static void handle_server_unregister(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_unregister *cmd = buf; + uint8_t status; + + DBG(""); + + status = unregister_app(cmd->server_if); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_UNREGISTER, status); +} + +static void handle_server_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_connect *cmd = buf; + uint8_t status; + bdaddr_t addr; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &addr); + + /* TODO: Handle transport flag */ + + status = handle_connect(cmd->server_if, &addr, cmd->is_direct); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_CONNECT, + status); +} + +static void handle_server_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_disconnect *cmd = buf; + struct app_connection *conn; + uint8_t status; + + DBG(""); + + /* TODO: should we care to match also bdaddr when conn_id is unique? */ + conn = queue_remove_if(app_connections, match_connection_by_id, + INT_TO_PTR(cmd->conn_id)); + destroy_connection(conn); + + status = HAL_STATUS_SUCCESS; + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_DISCONNECT, status); +} + +static void handle_server_add_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_add_service *cmd = buf; + struct hal_ev_gatt_server_service_added ev; + struct gatt_app *server; + struct gatt_db_attribute *service; + uint8_t status; + bt_uuid_t uuid; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + server = find_app_by_id(cmd->server_if); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + android2uuid(cmd->srvc_id.uuid, &uuid); + + service = gatt_db_add_service(gatt_db, &uuid, cmd->srvc_id.is_primary, + cmd->num_handles); + if (!service) { + status = HAL_STATUS_FAILED; + goto failed; + } + + ev.srvc_handle = gatt_db_attribute_get_handle(service); + if (!ev.srvc_handle) { + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + ev.srvc_id = cmd->srvc_id; + ev.server_if = cmd->server_if; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_SERVICE_ADDED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_SERVICE, status); +} + +static void handle_server_add_included_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_add_inc_service *cmd = buf; + struct hal_ev_gatt_server_inc_srvc_added ev; + struct gatt_app *server; + struct gatt_db_attribute *service, *include; + uint8_t status; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + server = find_app_by_id(cmd->server_if); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + service = gatt_db_get_attribute(gatt_db, cmd->service_handle); + if (!service) { + status = HAL_STATUS_FAILED; + goto failed; + } + + include = gatt_db_get_attribute(gatt_db, cmd->included_handle); + if (!include) { + status = HAL_STATUS_FAILED; + goto failed; + } + + service = gatt_db_service_add_included(service, include); + if (!service) { + status = HAL_STATUS_FAILED; + goto failed; + } + + ev.incl_srvc_handle = gatt_db_attribute_get_handle(service); + status = HAL_STATUS_SUCCESS; +failed: + ev.srvc_handle = cmd->service_handle; + ev.status = status; + ev.server_if = cmd->server_if; + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_INC_SRVC_ADDED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_INC_SERVICE, status); +} + +static bool is_service(const bt_uuid_t *type) +{ + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + if (!bt_uuid_cmp(&uuid, type)) + return true; + + bt_uuid16_create(&uuid, GATT_SND_SVC_UUID); + if (!bt_uuid_cmp(&uuid, type)) + return true; + + return false; +} + +static bool match_pending_dev_request(const void *data, const void *user_data) +{ + const struct pending_request *pending_request = data; + + return !pending_request->completed; +} + +static void send_dev_complete_response(struct gatt_device *device, + uint8_t opcode) +{ + size_t mtu; + uint8_t *rsp = g_attrib_get_buffer(device->attrib, &mtu); + struct pending_request *val; + uint16_t len = 0; + uint8_t error = 0; + + if (queue_isempty(device->pending_requests)) + return; + + if (queue_find(device->pending_requests, match_pending_dev_request, + NULL)) { + DBG("Still pending requests"); + return; + } + + val = queue_peek_head(device->pending_requests); + if (!val) { + error = ATT_ECODE_ATTR_NOT_FOUND; + goto done; + } + + if (val->error) { + error = val->error; + goto done; + } + + switch (opcode) { + case ATT_OP_READ_BY_TYPE_REQ: { + struct att_data_list *adl; + int iterator = 0; + int length; + struct queue *temp; + + temp = queue_new(); + + val = queue_pop_head(device->pending_requests); + if (!val) { + queue_destroy(temp, NULL); + error = ATT_ECODE_ATTR_NOT_FOUND; + goto done; + } + + if (val->error) { + queue_destroy(temp, NULL); + error = val->error; + destroy_pending_request(val); + goto done; + } + + length = val->length; + + while (val && val->length == length && val->error == 0) { + queue_push_tail(temp, val); + val = queue_pop_head(device->pending_requests); + } + + adl = att_data_list_alloc(queue_length(temp), + sizeof(uint16_t) + length); + + destroy_pending_request(val); + + val = queue_pop_head(temp); + while (val) { + uint8_t *value = adl->data[iterator++]; + uint16_t handle; + + handle = gatt_db_attribute_get_handle(val->attrib); + + put_le16(handle, value); + memcpy(&value[2], val->value, val->length); + + destroy_pending_request(val); + val = queue_pop_head(temp); + } + + len = enc_read_by_type_resp(adl, rsp, mtu); + + att_data_list_free(adl); + queue_destroy(temp, destroy_pending_request); + + break; + } + case ATT_OP_READ_BLOB_REQ: + len = enc_read_blob_resp(val->value, val->length, val->offset, + rsp, mtu); + break; + case ATT_OP_READ_REQ: + len = enc_read_resp(val->value, val->length, rsp, mtu); + break; + case ATT_OP_READ_BY_GROUP_REQ: { + struct att_data_list *adl; + int iterator = 0; + int length; + struct queue *temp; + + temp = queue_new(); + + val = queue_pop_head(device->pending_requests); + if (!val) { + queue_destroy(temp, NULL); + error = ATT_ECODE_ATTR_NOT_FOUND; + goto done; + } + + length = val->length; + + while (val && val->length == length) { + queue_push_tail(temp, val); + val = queue_pop_head(device->pending_requests); + } + + adl = att_data_list_alloc(queue_length(temp), + 2 * sizeof(uint16_t) + length); + + val = queue_pop_head(temp); + while (val) { + uint8_t *value = adl->data[iterator++]; + uint16_t start_handle, end_handle; + + gatt_db_attribute_get_service_handles(val->attrib, + &start_handle, + &end_handle); + + put_le16(start_handle, value); + put_le16(end_handle, &value[2]); + memcpy(&value[4], val->value, val->length); + + destroy_pending_request(val); + val = queue_pop_head(temp); + } + + len = enc_read_by_grp_resp(adl, rsp, mtu); + + att_data_list_free(adl); + queue_destroy(temp, destroy_pending_request); + + break; + } + case ATT_OP_FIND_BY_TYPE_REQ: { + GSList *list = NULL; + + val = queue_pop_head(device->pending_requests); + while (val) { + struct att_range *range; + const bt_uuid_t *type; + + /* Its find by type and value - filter by value here */ + if ((val->length != val->filter_vlen) || + memcmp(val->value, val->filter_value, + val->length)) { + + destroy_pending_request(val); + val = queue_pop_head(device->pending_requests); + continue; + } + + range = new0(struct att_range, 1); + range->start = gatt_db_attribute_get_handle( + val->attrib); + + type = gatt_db_attribute_get_type(val->attrib); + if (is_service(type)) + gatt_db_attribute_get_service_handles( + val->attrib, + NULL, + &range->end); + else + range->end = range->start; + + list = g_slist_append(list, range); + + destroy_pending_request(val); + val = queue_pop_head(device->pending_requests); + } + + if (list && !error) + len = enc_find_by_type_resp(list, rsp, mtu); + else + error = ATT_ECODE_ATTR_NOT_FOUND; + + g_slist_free_full(list, free); + + break; + } + case ATT_OP_EXEC_WRITE_REQ: + len = enc_exec_write_resp(rsp); + break; + case ATT_OP_WRITE_REQ: + len = enc_write_resp(rsp); + break; + case ATT_OP_PREP_WRITE_REQ: { + uint16_t handle; + + handle = gatt_db_attribute_get_handle(val->attrib); + len = enc_prep_write_resp(handle, val->offset, val->value, + val->length, rsp, mtu); + break; + } + default: + break; + } + +done: + if (!len) + len = enc_error_resp(opcode, 0x0000, error, rsp, mtu); + + g_attrib_send(device->attrib, 0, rsp, len, NULL, NULL, NULL); + + queue_remove_all(device->pending_requests, NULL, NULL, + destroy_pending_request); +} + +struct request_processing_data { + uint8_t opcode; + struct gatt_device *device; +}; + +static uint8_t check_device_permissions(struct gatt_device *device, + uint8_t opcode, uint32_t permissions) +{ + GIOChannel *io; + int sec_level; + + io = g_attrib_get_channel(device->attrib); + + if (!bt_io_get(io, NULL, BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_INVALID)) + return ATT_ECODE_UNLIKELY; + + DBG("opcode 0x%02x permissions %u sec_level %u", opcode, permissions, + sec_level); + + switch (opcode) { + case ATT_OP_SIGNED_WRITE_CMD: + if (!(permissions & GATT_PERM_WRITE_SIGNED)) + return ATT_ECODE_WRITE_NOT_PERM; + + if (permissions & GATT_PERM_WRITE_SIGNED_MITM) { + bool auth; + + if (bt_get_csrk(&device->bdaddr, true, NULL, NULL, + &auth) && auth) + break; + + return ATT_ECODE_AUTHENTICATION; + } + break; + case ATT_OP_READ_BY_TYPE_REQ: + case ATT_OP_READ_REQ: + case ATT_OP_READ_BLOB_REQ: + case ATT_OP_READ_MULTI_REQ: + case ATT_OP_READ_BY_GROUP_REQ: + case ATT_OP_FIND_BY_TYPE_REQ: + case ATT_OP_FIND_INFO_REQ: + if (!(permissions & GATT_PERM_READ)) + return ATT_ECODE_READ_NOT_PERM; + + if ((permissions & GATT_PERM_READ_MITM) && + sec_level < BT_SECURITY_HIGH) + return ATT_ECODE_AUTHENTICATION; + + if ((permissions & GATT_PERM_READ_ENCRYPTED) && + sec_level < BT_SECURITY_MEDIUM) + return ATT_ECODE_INSUFF_ENC; + + if (permissions & GATT_PERM_READ_AUTHORIZATION) + return ATT_ECODE_AUTHORIZATION; + break; + case ATT_OP_WRITE_REQ: + case ATT_OP_WRITE_CMD: + case ATT_OP_PREP_WRITE_REQ: + case ATT_OP_EXEC_WRITE_REQ: + if (!(permissions & GATT_PERM_WRITE)) + return ATT_ECODE_WRITE_NOT_PERM; + + if ((permissions & GATT_PERM_WRITE_MITM) && + sec_level < BT_SECURITY_HIGH) + return ATT_ECODE_AUTHENTICATION; + + if ((permissions & GATT_PERM_WRITE_ENCRYPTED) && + sec_level < BT_SECURITY_MEDIUM) + return ATT_ECODE_INSUFF_ENC; + + if (permissions & GATT_PERM_WRITE_AUTHORIZATION) + return ATT_ECODE_AUTHORIZATION; + break; + default: + return ATT_ECODE_UNLIKELY; + } + + return 0; +} + +static uint8_t err_to_att(int err) +{ + if (!err || (err > 0 && err < UINT8_MAX)) + return err; + + switch (err) { + case -ENOENT: + return ATT_ECODE_INVALID_HANDLE; + case -ENOMEM: + return ATT_ECODE_INSUFF_RESOURCES; + default: + return ATT_ECODE_UNLIKELY; + } +} + +static void attribute_read_cb(struct gatt_db_attribute *attrib, int err, + const uint8_t *value, size_t length, + void *user_data) +{ + struct pending_request *resp_data = user_data; + uint8_t error = err_to_att(err); + + resp_data->attrib = attrib; + resp_data->length = length; + resp_data->error = error; + + resp_data->completed = true; + + if (!length) + return; + + resp_data->value = malloc0(length); + if (!resp_data->value) { + resp_data->error = ATT_ECODE_INSUFF_RESOURCES; + + return; + } + + memcpy(resp_data->value, value, length); +} + +static void read_requested_attributes(void *data, void *user_data) +{ + struct pending_request *resp_data = data; + struct request_processing_data *process_data = user_data; + struct bt_att *att = g_attrib_get_att(process_data->device->attrib); + struct gatt_db_attribute *attrib; + uint32_t permissions; + uint8_t error; + + attrib = resp_data->attrib; + if (!attrib) { + resp_data->error = ATT_ECODE_ATTR_NOT_FOUND; + resp_data->completed = true; + return; + } + + permissions = gatt_db_attribute_get_permissions(attrib); + + /* + * Check if it is attribute we didn't declare permissions, like service + * declaration or included service. Set permissions to read only + */ + if (permissions == 0) + permissions = GATT_PERM_READ; + + error = check_device_permissions(process_data->device, + process_data->opcode, + permissions); + if (error != 0) { + resp_data->error = error; + resp_data->completed = true; + return; + } + + gatt_db_attribute_read(attrib, resp_data->offset, process_data->opcode, + att, attribute_read_cb, resp_data); +} + +static void process_dev_pending_requests(struct gatt_device *device, + uint8_t att_opcode) +{ + struct request_processing_data process_data; + + if (queue_isempty(device->pending_requests)) + return; + + process_data.device = device; + process_data.opcode = att_opcode; + + /* Process pending requests and prepare response */ + queue_foreach(device->pending_requests, read_requested_attributes, + &process_data); + + send_dev_complete_response(device, att_opcode); +} + +static struct pending_trans_data *conn_add_transact(struct app_connection *conn, + uint8_t opcode, + struct gatt_db_attribute *attrib, + unsigned int serial_id) +{ + struct pending_trans_data *transaction; + static int32_t trans_id = 1; + + transaction = new0(struct pending_trans_data, 1); + transaction->id = trans_id++; + transaction->opcode = opcode; + transaction->attrib = attrib; + transaction->serial_id = serial_id; + + queue_push_tail(conn->transactions, transaction); + + return transaction; +} + +static bool get_dst_addr(struct bt_att *att, bdaddr_t *dst) +{ + GIOChannel *io = NULL; + GError *gerr = NULL; + + io = g_io_channel_unix_new(bt_att_get_fd(att)); + if (!io) + return false; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_INVALID); + if (gerr) { + error("gatt: bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return false; + } + + g_io_channel_unref(io); + return true; +} + +static void read_cb(struct gatt_db_attribute *attrib, unsigned int id, + uint16_t offset, uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct pending_trans_data *transaction; + struct hal_ev_gatt_server_request_read ev; + struct gatt_app *app; + struct app_connection *conn; + int32_t app_id = PTR_TO_INT(user_data); + bdaddr_t bdaddr; + + DBG("id %u", id); + + app = find_app_by_id(app_id); + if (!app) { + error("gatt: read_cb, cound not found app id"); + goto failed; + } + + if (!get_dst_addr(att, &bdaddr)) { + error("gatt: read_cb, could not obtain dst BDADDR"); + goto failed; + } + + conn = find_conn(&bdaddr, app->id); + if (!conn) { + error("gatt: read_cb, cound not found connection"); + goto failed; + } + + memset(&ev, 0, sizeof(ev)); + + /* Store the request data, complete callback and transaction id */ + transaction = conn_add_transact(conn, opcode, attrib, id); + + bdaddr2android(&bdaddr, ev.bdaddr); + ev.conn_id = conn->id; + ev.attr_handle = gatt_db_attribute_get_handle(attrib); + ev.offset = offset; + ev.is_long = opcode == ATT_OP_READ_BLOB_REQ; + ev.trans_id = transaction->id; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_REQUEST_READ, + sizeof(ev), &ev); + + return; + +failed: + gatt_db_attribute_read_result(attrib, id, -ENOENT, NULL, 0); +} + +static void write_cb(struct gatt_db_attribute *attrib, unsigned int id, + uint16_t offset, const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, void *user_data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_gatt_server_request_write *ev = (void *) buf; + struct pending_trans_data *transaction; + struct gatt_app *app; + int32_t app_id = PTR_TO_INT(user_data); + struct app_connection *conn; + bdaddr_t bdaddr; + + DBG("id %u", id); + + app = find_app_by_id(app_id); + if (!app) { + error("gatt: write_cb could not found app id"); + goto failed; + } + + if (!get_dst_addr(att, &bdaddr)) { + error("gatt: write_cb, could not obtain dst BDADDR"); + goto failed; + } + + conn = find_conn(&bdaddr, app->id); + if (!conn) { + error("gatt: write_cb could not found connection"); + goto failed; + } + + /* + * Remember that this application has ongoing prep write + * Need it later to find out where to send execute write + */ + if (opcode == ATT_OP_PREP_WRITE_REQ) + conn->wait_execute_write = true; + + /* Store the request data, complete callback and transaction id */ + transaction = conn_add_transact(conn, opcode, attrib, id); + + memset(ev, 0, sizeof(*ev)); + + bdaddr2android(&bdaddr, &ev->bdaddr); + ev->attr_handle = gatt_db_attribute_get_handle(attrib); + ev->offset = offset; + + ev->conn_id = conn->id; + ev->trans_id = transaction->id; + + ev->is_prep = opcode == ATT_OP_PREP_WRITE_REQ; + + if (opcode == ATT_OP_WRITE_REQ || opcode == ATT_OP_PREP_WRITE_REQ) + ev->need_rsp = 0x01; + else + gatt_db_attribute_write_result(attrib, id, 0); + + ev->length = len; + memcpy(ev->value, value, len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_REQUEST_WRITE, + sizeof(*ev) + ev->length , ev); + return; + +failed: + gatt_db_attribute_write_result(attrib, id, ATT_ECODE_UNLIKELY); +} + +static uint32_t android_to_gatt_permissions(int32_t hal_permissions) +{ + uint32_t permissions = 0; + + if (hal_permissions & HAL_GATT_PERMISSION_READ) + permissions |= GATT_PERM_READ; + + if (hal_permissions & HAL_GATT_PERMISSION_READ_ENCRYPTED) + permissions |= GATT_PERM_READ_ENCRYPTED | GATT_PERM_READ; + + if (hal_permissions & HAL_GATT_PERMISSION_READ_ENCRYPTED_MITM) + permissions |= GATT_PERM_READ_MITM | GATT_PERM_READ_ENCRYPTED | + GATT_PERM_READ; + + if (hal_permissions & HAL_GATT_PERMISSION_WRITE) + permissions |= GATT_PERM_WRITE; + + if (hal_permissions & HAL_GATT_PERMISSION_WRITE_ENCRYPTED) + permissions |= GATT_PERM_WRITE_ENCRYPTED | GATT_PERM_WRITE; + + if (hal_permissions & HAL_GATT_PERMISSION_WRITE_ENCRYPTED_MITM) + permissions |= GATT_PERM_WRITE_MITM | + GATT_PERM_WRITE_ENCRYPTED | GATT_PERM_WRITE; + + if (hal_permissions & HAL_GATT_PERMISSION_WRITE_SIGNED) + permissions |= GATT_PERM_WRITE_SIGNED; + + if (hal_permissions & HAL_GATT_PERMISSION_WRITE_SIGNED_MITM) + permissions |= GATT_PERM_WRITE_SIGNED_MITM | + GATT_PERM_WRITE_SIGNED; + + return permissions; +} + +static void handle_server_add_characteristic(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_add_characteristic *cmd = buf; + struct hal_ev_gatt_server_characteristic_added ev; + struct gatt_app *server; + struct gatt_db_attribute *attrib; + bt_uuid_t uuid; + uint8_t status; + uint32_t permissions; + int32_t app_id = cmd->server_if; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + server = find_app_by_id(app_id); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + attrib = gatt_db_get_attribute(gatt_db, cmd->service_handle); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + android2uuid(cmd->uuid, &uuid); + permissions = android_to_gatt_permissions(cmd->permissions); + + attrib = gatt_db_service_add_characteristic(attrib, + &uuid, permissions, + cmd->properties, + read_cb, write_cb, + INT_TO_PTR(app_id)); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + ev.char_handle = gatt_db_attribute_get_handle(attrib); + status = HAL_STATUS_SUCCESS; + +failed: + ev.srvc_handle = cmd->service_handle; + ev.status = status; + ev.server_if = app_id; + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + memcpy(ev.uuid, cmd->uuid, sizeof(cmd->uuid)); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_CHAR_ADDED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_CHARACTERISTIC, status); +} + +static void handle_server_add_descriptor(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_add_descriptor *cmd = buf; + struct hal_ev_gatt_server_descriptor_added ev; + struct gatt_app *server; + struct gatt_db_attribute *attrib; + bt_uuid_t uuid; + uint8_t status; + uint32_t permissions; + int32_t app_id = cmd->server_if; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + server = find_app_by_id(app_id); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + android2uuid(cmd->uuid, &uuid); + permissions = android_to_gatt_permissions(cmd->permissions); + + attrib = gatt_db_get_attribute(gatt_db, cmd->service_handle); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + attrib = gatt_db_service_add_descriptor(attrib, &uuid, permissions, + read_cb, write_cb, + INT_TO_PTR(app_id)); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + ev.descr_handle = gatt_db_attribute_get_handle(attrib); + status = HAL_STATUS_SUCCESS; + +failed: + ev.server_if = app_id; + ev.srvc_handle = cmd->service_handle; + memcpy(ev.uuid, cmd->uuid, sizeof(cmd->uuid)); + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_DESCRIPTOR_ADDED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_DESCRIPTOR, status); +} + +static void notify_service_change(void *data, void *user_data) +{ + struct att_range range; + struct gatt_db_attribute *attrib = user_data; + + gatt_db_attribute_get_service_handles(attrib, &range.start, &range.end); + + /* In case of db error */ + if (!range.end) + return; + + notify_att_range_change(data, &range); +} + +static sdp_record_t *get_sdp_record(uuid_t *uuid, uint16_t start, uint16_t end, + const char *name) +{ + sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto; + uuid_t root_uuid, proto_uuid, l2cap; + sdp_record_t *record; + sdp_data_t *psm, *sh, *eh; + uint16_t lp = ATT_PSM; + + 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); + + svclass_id = sdp_list_append(NULL, uuid); + sdp_set_service_classes(record, svclass_id); + sdp_list_free(svclass_id, NULL); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&proto_uuid, ATT_UUID); + proto[1] = sdp_list_append(NULL, &proto_uuid); + sh = sdp_data_alloc(SDP_UINT16, &start); + proto[1] = sdp_list_append(proto[1], sh); + eh = sdp_data_alloc(SDP_UINT16, &end); + proto[1] = sdp_list_append(proto[1], eh); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + if (name) + sdp_set_info_attr(record, name, "BlueZ for Android", NULL); + + sdp_data_free(psm); + sdp_data_free(sh); + sdp_data_free(eh); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto, NULL); + + return record; +} + +static uint32_t add_sdp_record(const bt_uuid_t *uuid, uint16_t start, + uint16_t end, const char *name) +{ + sdp_record_t *rec; + uuid_t u, u32; + + switch (uuid->type) { + case BT_UUID16: + sdp_uuid16_create(&u, uuid->value.u16); + break; + case BT_UUID32: + sdp_uuid32_create(&u32, uuid->value.u32); + sdp_uuid32_to_uuid128(&u, &u32); + break; + case BT_UUID128: + sdp_uuid128_create(&u, &uuid->value.u128); + break; + case BT_UUID_UNSPEC: + default: + return 0; + } + + rec = get_sdp_record(&u, start, end, name); + if (!rec) + return 0; + + if (bt_adapter_add_record(rec, 0) < 0) { + error("gatt: Failed to register SDP record"); + sdp_record_free(rec); + return 0; + } + + return rec->handle; +} + +static bool match_service_sdp(const void *data, const void *user_data) +{ + const struct service_sdp *s = data; + + return s->service_handle == PTR_TO_INT(user_data); +} + +static struct service_sdp *new_service_sdp_record(int32_t service_handle) +{ + bt_uuid_t uuid; + struct service_sdp *s; + struct gatt_db_attribute *attrib; + uint16_t end_handle; + + attrib = gatt_db_get_attribute(gatt_db, service_handle); + if (!attrib) + return NULL; + + gatt_db_attribute_get_service_handles(attrib, NULL, &end_handle); + if (!end_handle) + return NULL; + + if (!gatt_db_attribute_get_service_uuid(attrib, &uuid)) + return NULL; + + s = new0(struct service_sdp, 1); + s->service_handle = service_handle; + s->sdp_handle = add_sdp_record(&uuid, service_handle, end_handle, NULL); + if (!s->sdp_handle) { + free(s); + return NULL; + } + + return s; +} + +static void free_service_sdp_record(void *data) +{ + struct service_sdp *s = data; + + if (!s) + return; + + bt_adapter_remove_record(s->sdp_handle); + free(s); +} + +static bool add_service_sdp_record(int32_t service_handle) +{ + struct service_sdp *s; + + s = queue_find(services_sdp, match_service_sdp, + INT_TO_PTR(service_handle)); + if (s) + return true; + + s = new_service_sdp_record(service_handle); + if (!s) + return false; + + queue_push_tail(services_sdp, s); + + return true; +} + +static void remove_service_sdp_record(int32_t service_handle) +{ + struct service_sdp *s; + + s = queue_remove_if(services_sdp, match_service_sdp, + INT_TO_PTR(service_handle)); + if (!s) + return; + + free_service_sdp_record(s); +} + +static void handle_server_start_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_start_service *cmd = buf; + struct hal_ev_gatt_server_service_started ev; + struct gatt_app *server; + struct gatt_db_attribute *attrib; + uint8_t status; + + DBG("transport 0x%02x", cmd->transport); + + memset(&ev, 0, sizeof(ev)); + + if (cmd->transport == 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + + server = find_app_by_id(cmd->server_if); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (cmd->transport & GATT_SERVER_TRANSPORT_BREDR_BIT) { + if (!add_service_sdp_record(cmd->service_handle)) { + status = HAL_STATUS_FAILED; + goto failed; + } + } + /* TODO: Handle BREDR only */ + + attrib = gatt_db_get_attribute(gatt_db, cmd->service_handle); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (!gatt_db_service_set_active(attrib, true)) { + /* + * no need to clean SDP since this can fail only if service + * handle is invalid in which case add_sdp_record() also fails + */ + status = HAL_STATUS_FAILED; + goto failed; + } + + queue_foreach(gatt_devices, notify_service_change, attrib); + + status = HAL_STATUS_SUCCESS; + +failed: + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + ev.server_if = cmd->server_if; + ev.srvc_handle = cmd->service_handle; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_SERVICE_STARTED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_START_SERVICE, status); +} + +static void handle_server_stop_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_stop_service *cmd = buf; + struct hal_ev_gatt_server_service_stopped ev; + struct gatt_app *server; + struct gatt_db_attribute *attrib; + uint8_t status; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + server = find_app_by_id(cmd->server_if); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + attrib = gatt_db_get_attribute(gatt_db, cmd->service_handle); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (!gatt_db_service_set_active(attrib, false)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + remove_service_sdp_record(cmd->service_handle); + + status = HAL_STATUS_SUCCESS; + + queue_foreach(gatt_devices, notify_service_change, attrib); + +failed: + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + ev.server_if = cmd->server_if; + ev.srvc_handle = cmd->service_handle; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_SERVICE_STOPPED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_STOP_SERVICE, status); +} + +static void handle_server_delete_service(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_delete_service *cmd = buf; + struct hal_ev_gatt_server_service_deleted ev; + struct gatt_app *server; + struct gatt_db_attribute *attrib; + uint8_t status; + + DBG(""); + + memset(&ev, 0, sizeof(ev)); + + server = find_app_by_id(cmd->server_if); + if (!server) { + status = HAL_STATUS_FAILED; + goto failed; + } + + attrib = gatt_db_get_attribute(gatt_db, cmd->service_handle); + if (!attrib) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (!gatt_db_remove_service(gatt_db, attrib)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + remove_service_sdp_record(cmd->service_handle); + + status = HAL_STATUS_SUCCESS; + +failed: + ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE; + ev.srvc_handle = cmd->service_handle; + ev.server_if = cmd->server_if; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_SERVICE_DELETED, sizeof(ev), &ev); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_DELETE_SERVICE, status); +} + +static void indication_confirmation_cb(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + struct hal_ev_gatt_server_indication_sent ev; + + ev.status = status; + ev.conn_id = PTR_TO_UINT(user_data); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_INDICATION_SENT, sizeof(ev), &ev); +} + +static void handle_server_send_indication(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_send_indication *cmd = buf; + struct app_connection *conn; + uint8_t status; + uint16_t length; + uint8_t *pdu; + size_t mtu; + GAttribResultFunc confirmation_cb = NULL; + + DBG(""); + + conn = find_connection_by_id(cmd->conn_id); + if (!conn) { + error("gatt: Could not find connection"); + status = HAL_STATUS_FAILED; + goto reply; + } + + pdu = g_attrib_get_buffer(conn->device->attrib, &mtu); + + if (cmd->confirm) { + length = enc_indication(cmd->attribute_handle, + (uint8_t *) cmd->value, cmd->len, pdu, + mtu); + confirmation_cb = indication_confirmation_cb; + } else { + length = enc_notification(cmd->attribute_handle, + (uint8_t *) cmd->value, + cmd->len, pdu, mtu); + } + + if (!g_attrib_send(conn->device->attrib, 0, pdu, length, + confirmation_cb, UINT_TO_PTR(conn->id), NULL)) { + error("gatt: Failed to send indication"); + status = HAL_STATUS_FAILED; + } else { + status = HAL_STATUS_SUCCESS; + } + + /* Here we confirm failed indications and all notifications */ + if (status || !confirmation_cb) + indication_confirmation_cb(status, NULL, 0, + UINT_TO_PTR(conn->id)); + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_SEND_INDICATION, status); +} + +static bool match_trans_id(const void *data, const void *user_data) +{ + const struct pending_trans_data *transaction = data; + + return transaction->id == PTR_TO_UINT(user_data); +} + +static bool find_conn_waiting_exec_write(const void *data, + const void *user_data) +{ + const struct app_connection *conn = data; + + return conn->wait_execute_write; +} + +static bool pending_execute_write(void) +{ + return queue_find(app_connections, find_conn_waiting_exec_write, NULL); +} + +static void handle_server_send_response(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_server_send_response *cmd = buf; + struct pending_trans_data *transaction; + struct app_connection *conn; + uint8_t status; + + DBG(""); + + conn = find_connection_by_id(cmd->conn_id); + if (!conn) { + error("gatt: could not found connection"); + status = HAL_STATUS_FAILED; + goto reply; + } + + transaction = queue_remove_if(conn->transactions, match_trans_id, + UINT_TO_PTR(cmd->trans_id)); + if (!transaction) { + error("gatt: transaction ID = %d not found", cmd->trans_id); + status = HAL_STATUS_FAILED; + goto reply; + } + + if (transaction->opcode == ATT_OP_EXEC_WRITE_REQ) { + struct pending_request *req; + + conn->wait_execute_write = false; + + /* Check for execute response from all server applications */ + if (pending_execute_write()) + goto done; + + /* + * This is usually done through db write callback but for + * execute write we dont have the attribute or handle to call + * gatt_db_attribute_write(). + */ + req = queue_peek_head(conn->device->pending_requests); + if (!req) + goto done; + + /* Cast status to uint8_t, due to (byte) cast in java layer. */ + req->error = err_to_att((uint8_t) cmd->status); + req->completed = true; + + /* + * FIXME: Handle situation when not all server applications + * respond with a success. + */ + } + + /* Cast status to uint8_t, due to (byte) cast in java layer. */ + if (transaction->opcode < ATT_OP_WRITE_REQ) + gatt_db_attribute_read_result(transaction->attrib, + transaction->serial_id, + err_to_att((uint8_t) cmd->status), + cmd->data, cmd->len); + else + gatt_db_attribute_write_result(transaction->attrib, + transaction->serial_id, + err_to_att((uint8_t) cmd->status)); + + send_dev_complete_response(conn->device, transaction->opcode); + +done: + /* Clean request data */ + free(transaction); + + status = HAL_STATUS_SUCCESS; + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_SEND_RESPONSE, status); +} + +static void handle_client_scan_filter_setup(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_scan_filter_setup *cmd = buf; + + DBG("client_if %u", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_SETUP, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_scan_filter_add_remove(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_scan_filter_add_remove *cmd = buf; + + DBG("client_if %u", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_ADD_REMOVE, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_scan_filter_clear(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_scan_filter_clear *cmd = buf; + + DBG("client_if %u", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_CLEAR, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_scan_filter_enable(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_scan_filter_enable *cmd = buf; + + DBG("client_if %u", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_ENABLE, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_configure_mtu(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_configure_mtu *cmd = buf; + static struct app_connection *conn; + uint8_t status; + + DBG("conn_id %u mtu %d", cmd->conn_id, cmd->mtu); + + conn = find_connection_by_id(cmd->conn_id); + if (!conn) { + status = HAL_STATUS_FAILED; + goto failed; + } + + /* + * currently MTU is always exchanged on connection, just report current + * value + * + * TODO figure out when send failed status in notification + * TODO should we fail for BR/EDR? + */ + notify_client_mtu_change(conn, false); + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_CONFIGURE_MTU, + status); +} + +static void handle_client_conn_param_update(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_conn_param_update *cmd = buf; + char address[18]; + bdaddr_t bdaddr; + + android2bdaddr(cmd->address, &bdaddr); + ba2str(&bdaddr, address); + + DBG("%s", address); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_CONN_PARAM_UPDATE, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_set_scan_param(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_set_scan_param *cmd = buf; + + DBG("interval %d window %d", cmd->interval, cmd->window); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SET_SCAN_PARAM, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_setup_multi_adv(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_setup_multi_adv *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_update_multi_adv(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_update_multi_adv *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_UPDATE_MULTI_ADV, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_setup_multi_adv_inst(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_setup_multi_adv_inst *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV_INST, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_disable_multi_adv_inst(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_disable_multi_adv_inst *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DISABLE_MULTI_ADV_INST, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_configure_batchscan(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_configure_batchscan *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_CONFIGURE_BATCHSCAN, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_enable_batchscan(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_enable_batchscan *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_ENABLE_BATCHSCAN, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_disable_batchscan(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_disable_batchscan *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DISABLE_BATCHSCAN, + HAL_STATUS_UNSUPPORTED); +} + +static void handle_client_read_batchscan_reports(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_read_batchscan_reports *cmd = buf; + + DBG("client_if %d", cmd->client_if); + + /* TODO */ + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_BATCHSCAN_REPORTS, + HAL_STATUS_UNSUPPORTED); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_GATT_CLIENT_REGISTER */ + { handle_client_register, false, + sizeof(struct hal_cmd_gatt_client_register) }, + /* HAL_OP_GATT_CLIENT_UNREGISTER */ + { handle_client_unregister, false, + sizeof(struct hal_cmd_gatt_client_unregister) }, + /* HAL_OP_GATT_CLIENT_SCAN */ + { handle_client_scan, false, + sizeof(struct hal_cmd_gatt_client_scan) }, + /* HAL_OP_GATT_CLIENT_CONNECT */ + { handle_client_connect, false, + sizeof(struct hal_cmd_gatt_client_connect) }, + /* HAL_OP_GATT_CLIENT_DISCONNECT */ + { handle_client_disconnect, false, + sizeof(struct hal_cmd_gatt_client_disconnect) }, + /* HAL_OP_GATT_CLIENT_LISTEN */ + { handle_client_listen, false, + sizeof(struct hal_cmd_gatt_client_listen) }, + /* HAL_OP_GATT_CLIENT_REFRESH */ + { handle_client_refresh, false, + sizeof(struct hal_cmd_gatt_client_refresh) }, + /* HAL_OP_GATT_CLIENT_SEARCH_SERVICE */ + { handle_client_search_service, true, + sizeof(struct hal_cmd_gatt_client_search_service) }, + /* HAL_OP_GATT_CLIENT_GET_INCLUDED_SERVICE */ + { handle_client_get_included_service, true, + sizeof(struct hal_cmd_gatt_client_get_included_service) }, + /* HAL_OP_GATT_CLIENT_GET_CHARACTERISTIC */ + { handle_client_get_characteristic, true, + sizeof(struct hal_cmd_gatt_client_get_characteristic) }, + /* HAL_OP_GATT_CLIENT_GET_DESCRIPTOR */ + { handle_client_get_descriptor, true, + sizeof(struct hal_cmd_gatt_client_get_descriptor) }, + /* HAL_OP_GATT_CLIENT_READ_CHARACTERISTIC */ + { handle_client_read_characteristic, false, + sizeof(struct hal_cmd_gatt_client_read_characteristic) }, + /* HAL_OP_GATT_CLIENT_WRITE_CHARACTERISTIC */ + { handle_client_write_characteristic, true, + sizeof(struct hal_cmd_gatt_client_write_characteristic) }, + /* HAL_OP_GATT_CLIENT_READ_DESCRIPTOR */ + { handle_client_read_descriptor, false, + sizeof(struct hal_cmd_gatt_client_read_descriptor) }, + /* HAL_OP_GATT_CLIENT_WRITE_DESCRIPTOR */ + { handle_client_write_descriptor, true, + sizeof(struct hal_cmd_gatt_client_write_descriptor) }, + /* HAL_OP_GATT_CLIENT_EXECUTE_WRITE */ + { handle_client_execute_write, false, + sizeof(struct hal_cmd_gatt_client_execute_write)}, + /* HAL_OP_GATT_CLIENT_REGISTER_FOR_NOTIFICATION */ + { handle_client_register_for_notification, false, + sizeof(struct hal_cmd_gatt_client_register_for_notification) }, + /* HAL_OP_GATT_CLIENT_DEREGISTER_FOR_NOTIFICATION */ + { handle_client_deregister_for_notification, false, + sizeof(struct hal_cmd_gatt_client_deregister_for_notification) }, + /* HAL_OP_GATT_CLIENT_READ_REMOTE_RSSI */ + { handle_client_read_remote_rssi, false, + sizeof(struct hal_cmd_gatt_client_read_remote_rssi) }, + /* HAL_OP_GATT_CLIENT_GET_DEVICE_TYPE */ + { handle_client_get_device_type, false, + sizeof(struct hal_cmd_gatt_client_get_device_type) }, + /* HAL_OP_GATT_CLIENT_SET_ADV_DATA */ + { handle_client_set_adv_data, true, + sizeof(struct hal_cmd_gatt_client_set_adv_data) }, + /* HAL_OP_GATT_CLIENT_TEST_COMMAND */ + { handle_client_test_command, false, + sizeof(struct hal_cmd_gatt_client_test_command) }, + /* HAL_OP_GATT_SERVER_REGISTER */ + { handle_server_register, false, + sizeof(struct hal_cmd_gatt_server_register) }, + /* HAL_OP_GATT_SERVER_UNREGISTER */ + { handle_server_unregister, false, + sizeof(struct hal_cmd_gatt_server_unregister) }, + /* HAL_OP_GATT_SERVER_CONNECT */ + { handle_server_connect, false, + sizeof(struct hal_cmd_gatt_server_connect) }, + /* HAL_OP_GATT_SERVER_DISCONNECT */ + { handle_server_disconnect, false, + sizeof(struct hal_cmd_gatt_server_disconnect) }, + /* HAL_OP_GATT_SERVER_ADD_SERVICE */ + { handle_server_add_service, false, + sizeof(struct hal_cmd_gatt_server_add_service) }, + /* HAL_OP_GATT_SERVER_ADD_INC_SERVICE */ + { handle_server_add_included_service, false, + sizeof(struct hal_cmd_gatt_server_add_inc_service) }, + /* HAL_OP_GATT_SERVER_ADD_CHARACTERISTIC */ + { handle_server_add_characteristic, false, + sizeof(struct hal_cmd_gatt_server_add_characteristic) }, + /* HAL_OP_GATT_SERVER_ADD_DESCRIPTOR */ + { handle_server_add_descriptor, false, + sizeof(struct hal_cmd_gatt_server_add_descriptor) }, + /* HAL_OP_GATT_SERVER_START_SERVICE */ + { handle_server_start_service, false, + sizeof(struct hal_cmd_gatt_server_start_service) }, + /* HAL_OP_GATT_SERVER_STOP_SERVICE */ + { handle_server_stop_service, false, + sizeof(struct hal_cmd_gatt_server_stop_service) }, + /* HAL_OP_GATT_SERVER_DELETE_SERVICE */ + { handle_server_delete_service, false, + sizeof(struct hal_cmd_gatt_server_delete_service) }, + /* HAL_OP_GATT_SERVER_SEND_INDICATION */ + { handle_server_send_indication, true, + sizeof(struct hal_cmd_gatt_server_send_indication) }, + /* HAL_OP_GATT_SERVER_SEND_RESPONSE */ + { handle_server_send_response, true, + sizeof(struct hal_cmd_gatt_server_send_response) }, + /* HAL_OP_GATT_CLIENT_SCAN_FILTER_SETUP */ + { handle_client_scan_filter_setup, false, + sizeof(struct hal_cmd_gatt_client_scan_filter_setup) }, + /* HAL_OP_GATT_CLIENT_SCAN_FILTER_ADD_REMOVE */ + { handle_client_scan_filter_add_remove, true, + sizeof(struct hal_cmd_gatt_client_scan_filter_add_remove) }, + /* HAL_OP_GATT_CLIENT_SCAN_FILTER_CLEAR */ + { handle_client_scan_filter_clear, false, + sizeof(struct hal_cmd_gatt_client_scan_filter_clear) }, + /* HAL_OP_GATT_CLIENT_SCAN_FILTER_ENABLE */ + { handle_client_scan_filter_enable, false, + sizeof(struct hal_cmd_gatt_client_scan_filter_enable) }, + /* HAL_OP_GATT_CLIENT_CONFIGURE_MTU */ + { handle_client_configure_mtu, false, + sizeof(struct hal_cmd_gatt_client_configure_mtu) }, + /* HAL_OP_GATT_CLIENT_CONN_PARAM_UPDATE */ + { handle_client_conn_param_update, false, + sizeof(struct hal_cmd_gatt_client_conn_param_update) }, + /* HAL_OP_GATT_CLIENT_SET_SCAN_PARAM */ + { handle_client_set_scan_param, false, + sizeof(struct hal_cmd_gatt_client_set_scan_param) }, + /* HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV */ + { handle_client_setup_multi_adv, false, + sizeof(struct hal_cmd_gatt_client_setup_multi_adv) }, + /* HAL_OP_GATT_CLIENT_UPDATE_MULTI_ADV */ + { handle_client_update_multi_adv, false, + sizeof(struct hal_cmd_gatt_client_update_multi_adv) }, + /* HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV_INST */ + { handle_client_setup_multi_adv_inst, false, + sizeof(struct hal_cmd_gatt_client_setup_multi_adv_inst) }, + /* HAL_OP_GATT_CLIENT_DISABLE_MULTI_ADV_INST */ + { handle_client_disable_multi_adv_inst, false, + sizeof(struct hal_cmd_gatt_client_disable_multi_adv_inst) }, + /* HAL_OP_GATT_CLIENT_CONFIGURE_BATCHSCAN */ + { handle_client_configure_batchscan, false, + sizeof(struct hal_cmd_gatt_client_configure_batchscan) }, + /* HAL_OP_GATT_CLIENT_ENABLE_BATCHSCAN */ + { handle_client_enable_batchscan, false, + sizeof(struct hal_cmd_gatt_client_enable_batchscan) }, + /* HAL_OP_GATT_CLIENT_DISABLE_BATCHSCAN */ + { handle_client_disable_batchscan, false, + sizeof(struct hal_cmd_gatt_client_disable_batchscan) }, + /* HAL_OP_GATT_CLIENT_READ_BATCHSCAN_REPORTS */ + { handle_client_read_batchscan_reports, false, + sizeof(struct hal_cmd_gatt_client_read_batchscan_reports) }, +}; + +static uint8_t read_by_type(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *device) +{ + uint16_t start, end; + uint16_t len = 0; + bt_uuid_t uuid; + struct queue *q; + + DBG(""); + + switch (cmd[0]) { + case ATT_OP_READ_BY_TYPE_REQ: + len = dec_read_by_type_req(cmd, cmd_len, &start, &end, &uuid); + break; + case ATT_OP_READ_BY_GROUP_REQ: + len = dec_read_by_grp_req(cmd, cmd_len, &start, &end, &uuid); + break; + default: + break; + } + + if (!len) + return ATT_ECODE_INVALID_PDU; + + if (start > end || start == 0) + return ATT_ECODE_INVALID_HANDLE; + + q = queue_new(); + + switch (cmd[0]) { + case ATT_OP_READ_BY_TYPE_REQ: + gatt_db_read_by_type(gatt_db, start, end, uuid, q); + break; + case ATT_OP_READ_BY_GROUP_REQ: + gatt_db_read_by_group_type(gatt_db, start, end, uuid, q); + break; + default: + break; + } + + if (queue_isempty(q)) { + queue_destroy(q, NULL); + return ATT_ECODE_ATTR_NOT_FOUND; + } + + while (queue_peek_head(q)) { + struct pending_request *data; + struct gatt_db_attribute *attrib = queue_pop_head(q); + + data = new0(struct pending_request, 1); + data->attrib = attrib; + queue_push_tail(device->pending_requests, data); + } + + queue_destroy(q, NULL); + process_dev_pending_requests(device, cmd[0]); + + return 0; +} + +static uint8_t read_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + struct gatt_db_attribute *attrib; + uint16_t handle; + uint16_t len; + uint16_t offset; + struct pending_request *data; + + DBG(""); + + switch (cmd[0]) { + case ATT_OP_READ_BLOB_REQ: + len = dec_read_blob_req(cmd, cmd_len, &handle, &offset); + if (!len) + return ATT_ECODE_INVALID_PDU; + break; + case ATT_OP_READ_REQ: + len = dec_read_req(cmd, cmd_len, &handle); + if (!len) + return ATT_ECODE_INVALID_PDU; + offset = 0; + break; + default: + error("gatt: Unexpected read type 0x%02x", cmd[0]); + return ATT_ECODE_REQ_NOT_SUPP; + } + + attrib = gatt_db_get_attribute(gatt_db, handle); + if (attrib == 0) + return ATT_ECODE_INVALID_HANDLE; + + data = new0(struct pending_request, 1); + data->offset = offset; + data->attrib = attrib; + queue_push_tail(dev->pending_requests, data); + + process_dev_pending_requests(dev, cmd[0]); + + return 0; +} + +static uint8_t mtu_att_handle(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + uint16_t rmtu, mtu, len; + size_t length; + uint8_t *rsp; + + DBG(""); + + len = dec_mtu_req(cmd, cmd_len, &rmtu); + if (!len) + return ATT_ECODE_INVALID_PDU; + + /* MTU exchange shall not be used on BR/EDR - Vol 3. Part G. 4.3.1 */ + if (get_cid(dev) != ATT_CID) + return ATT_ECODE_UNLIKELY; + + if (!get_local_mtu(dev, &mtu)) + return ATT_ECODE_UNLIKELY; + + if (!update_mtu(dev, rmtu)) + return ATT_ECODE_UNLIKELY; + + rsp = g_attrib_get_buffer(dev->attrib, &length); + + /* Respond with our MTU */ + len = enc_mtu_resp(mtu, rsp, length); + if (!g_attrib_send(dev->attrib, 0, rsp, len, NULL, NULL, NULL)) + return ATT_ECODE_UNLIKELY; + + return 0; +} + +static uint8_t find_info_handle(const uint8_t *cmd, uint16_t cmd_len, + uint8_t *rsp, size_t rsp_size, uint16_t *length) +{ + struct gatt_db_attribute *attrib; + struct queue *q, *temp; + struct att_data_list *adl; + int iterator = 0; + uint16_t start, end; + uint16_t len, queue_len; + uint8_t format; + uint8_t ret = 0; + + DBG(""); + + len = dec_find_info_req(cmd, cmd_len, &start, &end); + if (!len) + return ATT_ECODE_INVALID_PDU; + + if (start > end || start == 0) + return ATT_ECODE_INVALID_HANDLE; + + q = queue_new(); + + gatt_db_find_information(gatt_db, start, end, q); + + if (queue_isempty(q)) { + queue_destroy(q, NULL); + return ATT_ECODE_ATTR_NOT_FOUND; + } + + temp = queue_new(); + + attrib = queue_peek_head(q); + /* UUIDS can be only 128 bit and 16 bit */ + len = bt_uuid_len(gatt_db_attribute_get_type(attrib)); + if (len != 2 && len != 16) { + queue_destroy(q, NULL); + queue_destroy(temp, NULL); + return ATT_ECODE_UNLIKELY; + } + + while (attrib) { + const bt_uuid_t *type; + + type = gatt_db_attribute_get_type(attrib); + if (bt_uuid_len(type) != len) + break; + + queue_push_tail(temp, queue_pop_head(q)); + attrib = queue_peek_head(q); + } + + queue_destroy(q, NULL); + + queue_len = queue_length(temp); + adl = att_data_list_alloc(queue_len, len + sizeof(uint16_t)); + if (!adl) { + queue_destroy(temp, NULL); + return ATT_ECODE_INSUFF_RESOURCES; + } + + while (queue_peek_head(temp)) { + uint8_t *value; + const bt_uuid_t *type; + struct gatt_db_attribute *attrib = queue_pop_head(temp); + uint16_t handle; + + type = gatt_db_attribute_get_type(attrib); + if (!type) + break; + + value = adl->data[iterator++]; + + handle = gatt_db_attribute_get_handle(attrib); + put_le16(handle, value); + memcpy(&value[2], &type->value, len); + } + + if (len == 2) + format = ATT_FIND_INFO_RESP_FMT_16BIT; + else + format = ATT_FIND_INFO_RESP_FMT_128BIT; + + len = enc_find_info_resp(format, adl, rsp, rsp_size); + if (!len) + ret = ATT_ECODE_UNLIKELY; + + *length = len; + att_data_list_free(adl); + queue_destroy(temp, NULL); + + return ret; +} + +struct find_by_type_request_data { + struct gatt_device *device; + uint8_t *search_value; + size_t search_vlen; + uint8_t error; +}; + +static void find_by_type_request_cb(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct find_by_type_request_data *find_data = user_data; + struct pending_request *request_data; + + if (find_data->error) + return; + + request_data = new0(struct pending_request, 1); + request_data->filter_value = malloc0(find_data->search_vlen); + if (!request_data->filter_value) { + destroy_pending_request(request_data); + find_data->error = ATT_ECODE_INSUFF_RESOURCES; + return; + } + + request_data->attrib = attrib; + request_data->filter_vlen = find_data->search_vlen; + memcpy(request_data->filter_value, find_data->search_value, + find_data->search_vlen); + + queue_push_tail(find_data->device->pending_requests, request_data); +} + +static uint8_t find_by_type_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *device) +{ + uint8_t search_value[cmd_len]; + size_t search_vlen; + uint16_t start, end; + bt_uuid_t uuid; + uint16_t len; + struct find_by_type_request_data data; + + DBG(""); + + len = dec_find_by_type_req(cmd, cmd_len, &start, &end, &uuid, + search_value, &search_vlen); + if (!len) + return ATT_ECODE_INVALID_PDU; + + if (start > end || start == 0) + return ATT_ECODE_INVALID_HANDLE; + + data.error = 0; + data.search_vlen = search_vlen; + data.search_value = search_value; + data.device = device; + + if (gatt_db_find_by_type(gatt_db, start, end, &uuid, + find_by_type_request_cb, &data) == 0) { + size_t mtu; + uint8_t *rsp = g_attrib_get_buffer(device->attrib, &mtu); + + len = enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, rsp, mtu); + g_attrib_send(device->attrib, 0, rsp, len, NULL, NULL, NULL); + return 0; + } + + if (!data.error) + process_dev_pending_requests(device, ATT_OP_FIND_BY_TYPE_REQ); + + return data.error; +} + +static void write_confirm(struct gatt_db_attribute *attrib, + int err, void *user_data) +{ + if (!err) + return; + + error("Error writting attribute %p", attrib); +} + +static void write_cmd_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + uint8_t value[cmd_len]; + struct gatt_db_attribute *attrib; + uint32_t permissions; + uint16_t handle; + uint16_t len; + size_t vlen; + + len = dec_write_cmd(cmd, cmd_len, &handle, value, &vlen); + if (!len) + return; + + if (handle == 0) + return; + + attrib = gatt_db_get_attribute(gatt_db, handle); + if (!attrib) + return; + + permissions = gatt_db_attribute_get_permissions(attrib); + + if (check_device_permissions(dev, cmd[0], permissions)) + return; + + gatt_db_attribute_write(attrib, 0, value, vlen, cmd[0], + g_attrib_get_att(dev->attrib), + write_confirm, NULL); +} + +static void write_signed_cmd_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + uint8_t value[cmd_len]; + uint8_t s[ATT_SIGNATURE_LEN]; + struct gatt_db_attribute *attrib; + uint32_t permissions; + uint16_t handle; + uint16_t len; + size_t vlen; + uint8_t csrk[16]; + uint32_t sign_cnt; + + if (get_cid(dev) != ATT_CID) { + error("gatt: Remote tries write signed on BR/EDR bearer"); + connection_cleanup(dev); + return; + } + + if (get_sec_level(dev) != BT_SECURITY_LOW) { + error("gatt: Remote tries write signed on encrypted link"); + connection_cleanup(dev); + return; + } + + if (!bt_get_csrk(&dev->bdaddr, false, csrk, &sign_cnt, NULL)) { + error("gatt: No valid csrk from remote device"); + return; + } + + len = dec_signed_write_cmd(cmd, cmd_len, &handle, value, &vlen, s); + + if (handle == 0) + return; + + attrib = gatt_db_get_attribute(gatt_db, handle); + if (!attrib) + return; + + permissions = gatt_db_attribute_get_permissions(attrib); + + if (check_device_permissions(dev, cmd[0], permissions)) + return; + + if (len) { + uint8_t t[ATT_SIGNATURE_LEN]; + uint32_t r_sign_cnt = get_le32(s); + + if (r_sign_cnt < sign_cnt) { + error("gatt: Invalid sign counter (%d<%d)", + r_sign_cnt, sign_cnt); + return; + } + + /* Generate signature and verify it */ + if (!bt_crypto_sign_att(crypto, csrk, cmd, + cmd_len - ATT_SIGNATURE_LEN, + r_sign_cnt, t)) { + error("gatt: Error when generating att signature"); + return; + } + + if (memcmp(t, s, ATT_SIGNATURE_LEN)) { + error("gatt: signature does not match"); + return; + } + /* Signature OK, proceed with write */ + bt_update_sign_counter(&dev->bdaddr, false, r_sign_cnt); + gatt_db_attribute_write(attrib, 0, value, vlen, cmd[0], + g_attrib_get_att(dev->attrib), + write_confirm, NULL); + } +} + +static void attribute_write_cb(struct gatt_db_attribute *attrib, int err, + void *user_data) +{ + struct pending_request *data = user_data; + uint8_t error = err_to_att(err); + + DBG(""); + + data->attrib = attrib; + data->error = error; + data->completed = true; +} + +static uint8_t write_req_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + uint8_t value[cmd_len]; + struct pending_request *data; + struct gatt_db_attribute *attrib; + uint32_t permissions; + uint16_t handle; + uint16_t len; + uint8_t error; + size_t vlen; + + len = dec_write_req(cmd, cmd_len, &handle, value, &vlen); + if (!len) + return ATT_ECODE_INVALID_PDU; + + if (handle == 0) + return ATT_ECODE_INVALID_HANDLE; + + attrib = gatt_db_get_attribute(gatt_db, handle); + if (!attrib) + return ATT_ECODE_ATTR_NOT_FOUND; + + permissions = gatt_db_attribute_get_permissions(attrib); + + error = check_device_permissions(dev, cmd[0], permissions); + if (error) + return error; + + data = new0(struct pending_request, 1); + data->attrib = attrib; + + queue_push_tail(dev->pending_requests, data); + + if (!gatt_db_attribute_write(attrib, 0, value, vlen, cmd[0], + g_attrib_get_att(dev->attrib), + attribute_write_cb, data)) { + queue_remove(dev->pending_requests, data); + free(data); + return ATT_ECODE_UNLIKELY; + } + + send_dev_complete_response(dev, cmd[0]); + + return 0; +} + +static uint8_t write_prep_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + uint8_t value[cmd_len]; + struct pending_request *data; + struct gatt_db_attribute *attrib; + uint32_t permissions; + uint16_t handle; + uint16_t offset; + uint8_t error; + uint16_t len; + size_t vlen; + + len = dec_prep_write_req(cmd, cmd_len, &handle, &offset, + value, &vlen); + if (!len) + return ATT_ECODE_INVALID_PDU; + + if (handle == 0) + return ATT_ECODE_INVALID_HANDLE; + + attrib = gatt_db_get_attribute(gatt_db, handle); + if (!attrib) + return ATT_ECODE_ATTR_NOT_FOUND; + + permissions = gatt_db_attribute_get_permissions(attrib); + + error = check_device_permissions(dev, cmd[0], permissions); + if (error) + return error; + + data = new0(struct pending_request, 1); + data->attrib = attrib; + data->offset = offset; + + queue_push_tail(dev->pending_requests, data); + + data->value = g_memdup(value, vlen); + data->length = vlen; + + if (!gatt_db_attribute_write(attrib, offset, value, vlen, cmd[0], + g_attrib_get_att(dev->attrib), + attribute_write_cb, data)) { + queue_remove(dev->pending_requests, data); + g_free(data->value); + free(data); + + return ATT_ECODE_UNLIKELY; + } + + send_dev_complete_response(dev, cmd[0]); + + return 0; +} + +static void send_server_write_execute_notify(void *data, void *user_data) +{ + struct hal_ev_gatt_server_request_exec_write *ev = user_data; + struct pending_trans_data *transaction; + struct app_connection *conn = data; + + if (!conn->wait_execute_write) + return; + + ev->conn_id = conn->id; + + transaction = conn_add_transact(conn, ATT_OP_EXEC_WRITE_REQ, NULL, 0); + + ev->trans_id = transaction->id; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_SERVER_REQUEST_EXEC_WRITE, sizeof(*ev), ev); +} + +static uint8_t write_execute_request(const uint8_t *cmd, uint16_t cmd_len, + struct gatt_device *dev) +{ + struct hal_ev_gatt_server_request_exec_write ev; + uint8_t value; + struct pending_request *data; + + /* + * Check if there was any write prep before. + * TODO: Try to find better error code if possible + */ + if (!pending_execute_write()) + return ATT_ECODE_UNLIKELY; + + if (!dec_exec_write_req(cmd, cmd_len, &value)) + return ATT_ECODE_INVALID_PDU; + + memset(&ev, 0, sizeof(ev)); + bdaddr2android(&dev->bdaddr, &ev.bdaddr); + ev.exec_write = value; + + data = new0(struct pending_request, 1); + + queue_push_tail(dev->pending_requests, data); + + queue_foreach(app_connections, send_server_write_execute_notify, &ev); + send_dev_complete_response(dev, cmd[0]); + + return 0; +} + +static void att_handler(const uint8_t *ipdu, uint16_t len, gpointer user_data) +{ + struct gatt_device *dev = user_data; + uint8_t status; + uint16_t resp_length = 0; + size_t length; + uint8_t *opdu = g_attrib_get_buffer(dev->attrib, &length); + + DBG("op 0x%02x", ipdu[0]); + + if (len > length) { + error("gatt: Too much data on ATT socket %p", opdu); + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + switch (ipdu[0]) { + case ATT_OP_READ_BY_GROUP_REQ: + case ATT_OP_READ_BY_TYPE_REQ: + status = read_by_type(ipdu, len, dev); + break; + case ATT_OP_READ_REQ: + case ATT_OP_READ_BLOB_REQ: + status = read_request(ipdu, len, dev); + break; + case ATT_OP_MTU_REQ: + status = mtu_att_handle(ipdu, len, dev); + break; + case ATT_OP_FIND_INFO_REQ: + status = find_info_handle(ipdu, len, opdu, length, + &resp_length); + break; + case ATT_OP_WRITE_REQ: + status = write_req_request(ipdu, len, dev); + break; + case ATT_OP_WRITE_CMD: + write_cmd_request(ipdu, len, dev); + /* No response on write cmd */ + return; + case ATT_OP_SIGNED_WRITE_CMD: + write_signed_cmd_request(ipdu, len, dev); + /* No response on write signed cmd */ + return; + case ATT_OP_PREP_WRITE_REQ: + status = write_prep_request(ipdu, len, dev); + break; + case ATT_OP_FIND_BY_TYPE_REQ: + status = find_by_type_request(ipdu, len, dev); + break; + case ATT_OP_EXEC_WRITE_REQ: + status = write_execute_request(ipdu, len, dev); + break; + case ATT_OP_READ_MULTI_REQ: + default: + DBG("Unsupported request 0x%02x", ipdu[0]); + status = ATT_ECODE_REQ_NOT_SUPP; + break; + } + +done: + if (status) + resp_length = enc_error_resp(ipdu[0], 0x0000, status, opdu, + length); + + g_attrib_send(dev->attrib, 0, opdu, resp_length, NULL, NULL, NULL); +} + +static void connect_confirm(GIOChannel *io, void *user_data) +{ + struct gatt_device *dev; + bdaddr_t dst; + GError *gerr = NULL; + + DBG(""); + + bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID); + if (gerr) { + error("gatt: bt_io_get: %s", gerr->message); + g_error_free(gerr); + return; + } + + /* TODO Handle collision */ + dev = find_device_by_addr(&dst); + if (!dev) { + dev = create_device(&dst); + } else { + if ((dev->state != DEVICE_DISCONNECTED) && + !(dev->state == DEVICE_CONNECT_INIT && + bt_kernel_conn_control())) { + char addr[18]; + + ba2str(&dst, addr); + info("gatt: Rejecting incoming connection from %s", + addr); + goto drop; + } + } + + if (!bt_io_accept(io, connect_cb, device_ref(dev), NULL, NULL)) { + error("gatt: failed to accept connection"); + device_unref(dev); + goto drop; + } + + queue_foreach(listen_apps, create_app_connection, dev); + device_set_state(dev, DEVICE_CONNECT_READY); + + return; + +drop: + g_io_channel_shutdown(io, TRUE, NULL); +} + +struct gap_srvc_handles { + struct gatt_db_attribute *srvc; + + /* Characteristics */ + struct gatt_db_attribute *dev_name; + struct gatt_db_attribute *appear; + struct gatt_db_attribute *priv; +}; + +static struct gap_srvc_handles gap_srvc_data; + +#define APPEARANCE_GENERIC_PHONE 0x0040 +#define PERIPHERAL_PRIVACY_DISABLE 0x00 + +static void device_name_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + const char *name = bt_get_adapter_name(); + + gatt_db_attribute_read_result(attrib, id, 0, (void *) name, + strlen(name)); +} + +static void register_gap_service(void) +{ + uint16_t start, end; + bt_uuid_t uuid; + + /* GAP UUID */ + bt_uuid16_create(&uuid, 0x1800); + gap_srvc_data.srvc = gatt_db_add_service(gatt_db, &uuid, true, 7); + + /* Device name characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gap_srvc_data.dev_name = + gatt_db_service_add_characteristic(gap_srvc_data.srvc, + &uuid, GATT_PERM_READ, + GATT_CHR_PROP_READ, + device_name_read_cb, + NULL, NULL); + + /* Appearance */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + + gap_srvc_data.appear = + gatt_db_service_add_characteristic(gap_srvc_data.srvc, + &uuid, GATT_PERM_READ, + GATT_CHR_PROP_READ, + NULL, NULL, NULL); + if (gap_srvc_data.appear) { + uint16_t value; + /* Store appearance into db */ + value = cpu_to_le16(APPEARANCE_GENERIC_PHONE); + gatt_db_attribute_write(gap_srvc_data.appear, 0, + (void *) &value, sizeof(value), + ATT_OP_WRITE_REQ, NULL, + write_confirm, NULL); + } + + /* Pripheral privacy flag */ + bt_uuid16_create(&uuid, GATT_CHARAC_PERIPHERAL_PRIV_FLAG); + gap_srvc_data.priv = + gatt_db_service_add_characteristic(gap_srvc_data.srvc, + &uuid, GATT_PERM_READ, + GATT_CHR_PROP_READ, + NULL, NULL, NULL); + if (gap_srvc_data.priv) { + uint8_t value; + + /* Store privacy into db */ + value = PERIPHERAL_PRIVACY_DISABLE; + gatt_db_attribute_write(gap_srvc_data.priv, 0, + &value, sizeof(value), + ATT_OP_WRITE_REQ, NULL, + write_confirm, NULL); + } + + gatt_db_service_set_active(gap_srvc_data.srvc , true); + + /* SDP */ + bt_uuid16_create(&uuid, 0x1800); + gatt_db_attribute_get_service_handles(gap_srvc_data.srvc, &start, &end); + gap_sdp_handle = add_sdp_record(&uuid, start, end, + "Generic Access Profile"); + if (!gap_sdp_handle) + error("gatt: Failed to register GAP SDP record"); +} + +static void device_info_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + char *buf = user_data; + + gatt_db_attribute_read_result(attrib, id, 0, user_data, strlen(buf)); +} + +static void device_info_read_system_id_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t pdu[8]; + + put_le64(bt_config_get_system_id(), pdu); + + gatt_db_attribute_read_result(attrib, id, 0, pdu, sizeof(pdu)); +} + +static void device_info_read_pnp_id_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t pdu[7]; + + pdu[0] = bt_config_get_pnp_source(); + put_le16(bt_config_get_pnp_vendor(), &pdu[1]); + put_le16(bt_config_get_pnp_product(), &pdu[3]); + put_le16(bt_config_get_pnp_version(), &pdu[5]); + + gatt_db_attribute_read_result(attrib, id, 0, pdu, sizeof(pdu)); +} + +static void register_device_info_service(void) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service; + uint16_t start_handle, end_handle; + const char *data; + uint32_t enc_perm = GATT_PERM_READ | GATT_PERM_READ_ENCRYPTED; + + DBG(""); + + /* Device Information Service */ + bt_uuid16_create(&uuid, 0x180a); + service = gatt_db_add_service(gatt_db, &uuid, true, 17); + + /* User data are not const hence (void *) cast is used */ + data = bt_config_get_name(); + if (data) { + bt_uuid16_create(&uuid, GATT_CHARAC_MODEL_NUMBER_STRING); + gatt_db_service_add_characteristic(service, &uuid, + GATT_PERM_READ, + GATT_CHR_PROP_READ, + device_info_read_cb, NULL, + (void *) data); + } + + data = bt_config_get_serial(); + if (data) { + bt_uuid16_create(&uuid, GATT_CHARAC_SERIAL_NUMBER_STRING); + gatt_db_service_add_characteristic(service, &uuid, + enc_perm, GATT_CHR_PROP_READ, + device_info_read_cb, NULL, + (void *) data); + } + + if (bt_config_get_system_id()) { + bt_uuid16_create(&uuid, GATT_CHARAC_SYSTEM_ID); + gatt_db_service_add_characteristic(service, &uuid, + enc_perm, GATT_CHR_PROP_READ, + device_info_read_system_id_cb, + NULL, NULL); + } + + data = bt_config_get_fw_rev(); + if (data) { + bt_uuid16_create(&uuid, GATT_CHARAC_FIRMWARE_REVISION_STRING); + gatt_db_service_add_characteristic(service, &uuid, + GATT_PERM_READ, + GATT_CHR_PROP_READ, + device_info_read_cb, NULL, + (void *) data); + } + + data = bt_config_get_hw_rev(); + if (data) { + bt_uuid16_create(&uuid, GATT_CHARAC_HARDWARE_REVISION_STRING); + gatt_db_service_add_characteristic(service, &uuid, + GATT_PERM_READ, + GATT_CHR_PROP_READ, + device_info_read_cb, NULL, + (void *) data); + } + + bt_uuid16_create(&uuid, GATT_CHARAC_SOFTWARE_REVISION_STRING); + gatt_db_service_add_characteristic(service, &uuid, GATT_PERM_READ, + GATT_CHR_PROP_READ, device_info_read_cb, + NULL, VERSION); + + data = bt_config_get_vendor(); + if (data) { + bt_uuid16_create(&uuid, GATT_CHARAC_MANUFACTURER_NAME_STRING); + gatt_db_service_add_characteristic(service, &uuid, + GATT_PERM_READ, + GATT_CHR_PROP_READ, + device_info_read_cb, NULL, + (void *) data); + } + + if (bt_config_get_pnp_source()) { + bt_uuid16_create(&uuid, GATT_CHARAC_PNP_ID); + gatt_db_service_add_characteristic(service, &uuid, + GATT_PERM_READ, + GATT_CHR_PROP_READ, + device_info_read_pnp_id_cb, + NULL, NULL); + } + + gatt_db_service_set_active(service, true); + + /* SDP */ + bt_uuid16_create(&uuid, 0x180a); + gatt_db_attribute_get_service_handles(service, &start_handle, + &end_handle); + dis_sdp_handle = add_sdp_record(&uuid, start_handle, end_handle, + "Device Information Service"); + if (!dis_sdp_handle) + error("gatt: Failed to register DIS SDP record"); +} + +static void gatt_srvc_change_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct gatt_device *dev; + bdaddr_t bdaddr; + + if (!get_dst_addr(att, &bdaddr)) { + error("gatt: srvc_change_write_cb, could not obtain BDADDR"); + return; + } + + dev = find_device_by_addr(&bdaddr); + if (!dev) { + error("gatt: Could not find device ?!"); + return; + } + + if (!bt_device_is_bonded(&bdaddr)) { + gatt_db_attribute_write_result(attrib, id, + ATT_ECODE_AUTHORIZATION); + return; + } + + /* 2 octets are expected as CCC value */ + if (len != 2) { + gatt_db_attribute_write_result(attrib, id, + ATT_ECODE_INVAL_ATTR_VALUE_LEN); + return; + } + + /* Set services changed indication value */ + bt_store_gatt_ccc(&bdaddr, get_le16(value)); + + gatt_db_attribute_write_result(attrib, id, 0); +} + +static void gatt_srvc_change_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct gatt_device *dev; + uint8_t pdu[2]; + bdaddr_t bdaddr; + + if (!get_dst_addr(att, &bdaddr)) { + error("gatt: srvc_change_read_cb, could not obtain BDADDR"); + return; + } + + dev = find_device_by_addr(&bdaddr); + if (!dev) { + error("gatt: Could not find device ?!"); + return; + } + + put_le16(bt_get_gatt_ccc(&dev->bdaddr), pdu); + + gatt_db_attribute_read_result(attrib, id, 0, pdu, sizeof(pdu)); +} + +static void register_gatt_service(void) +{ + struct gatt_db_attribute *service; + uint16_t start_handle, end_handle; + bt_uuid_t uuid; + + DBG(""); + + bt_uuid16_create(&uuid, 0x1801); + service = gatt_db_add_service(gatt_db, &uuid, true, 4); + + bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); + service_changed_attrib = gatt_db_service_add_characteristic(service, + &uuid, GATT_PERM_NONE, + GATT_CHR_PROP_INDICATE, + NULL, NULL, NULL); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + gatt_db_service_add_descriptor(service, &uuid, + GATT_PERM_READ | GATT_PERM_WRITE, + gatt_srvc_change_read_cb, + gatt_srvc_change_write_cb, NULL); + + gatt_db_service_set_active(service, true); + + /* SDP */ + bt_uuid16_create(&uuid, 0x1801); + gatt_db_attribute_get_service_handles(service, &start_handle, + &end_handle); + gatt_sdp_handle = add_sdp_record(&uuid, start_handle, end_handle, + "Generic Attribute Profile"); + + if (!gatt_sdp_handle) + error("gatt: Failed to register GATT SDP record"); +} + +static bool start_listening(void) +{ + /* BR/EDR socket */ + bredr_io = bt_io_listen(NULL, connect_confirm, NULL, NULL, NULL, + BT_IO_OPT_SOURCE_TYPE, BDADDR_BREDR, + BT_IO_OPT_PSM, ATT_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + + /* LE socket */ + le_io = bt_io_listen(NULL, connect_confirm, NULL, NULL, NULL, + BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + + if (!le_io && !bredr_io) { + error("gatt: Failed to start listening IO"); + return false; + } + + return true; +} + +static void gatt_paired_cb(const bdaddr_t *addr) +{ + struct gatt_device *dev; + char address[18]; + struct app_connection *conn; + + dev = find_device_by_addr(addr); + if (!dev) + return; + + ba2str(addr, address); + DBG("Paired device %s", address); + + /* conn without app is internal one used for search primary services */ + conn = find_conn_without_app(dev); + if (!conn) + return; + + if (conn->timeout_id > 0) { + g_source_remove(conn->timeout_id); + conn->timeout_id = 0; + } + + search_dev_for_srvc(conn, NULL); +} + +static void gatt_unpaired_cb(const bdaddr_t *addr) +{ + struct gatt_device *dev; + char address[18]; + + dev = find_device_by_addr(addr); + if (!dev) + return; + + ba2str(addr, address); + DBG("Unpaired device %s", address); + + queue_remove(gatt_devices, dev); + destroy_device(dev); +} + +bool bt_gatt_register(struct ipc *ipc, const bdaddr_t *addr) +{ + DBG(""); + + if (!bt_paired_register(gatt_paired_cb)) { + error("gatt: Could not register paired callback"); + return false; + } + + if (!bt_unpaired_register(gatt_unpaired_cb)) { + error("gatt: Could not register unpaired callback"); + return false; + } + + if (!start_listening()) + return false; + + crypto = bt_crypto_new(); + if (!crypto) { + error("gatt: Failed to setup crypto"); + goto failed; + } + + gatt_devices = queue_new(); + gatt_apps = queue_new(); + app_connections = queue_new(); + listen_apps = queue_new(); + services_sdp = queue_new(); + gatt_db = gatt_db_new(); + + if (!gatt_db) { + error("gatt: Failed to allocate memory for database"); + goto failed; + } + + if (!bt_le_register(le_device_found_handler)) { + error("gatt: bt_le_register failed"); + goto failed; + } + + bacpy(&adapter_addr, addr); + + hal_ipc = ipc; + + ipc_register(hal_ipc, HAL_SERVICE_ID_GATT, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + register_gap_service(); + register_device_info_service(); + register_gatt_service(); + + info("gatt: LE: %s BR/EDR: %s", le_io ? "enabled" : "disabled", + bredr_io ? "enabled" : "disabled"); + + return true; + +failed: + + bt_paired_unregister(gatt_paired_cb); + bt_unpaired_unregister(gatt_unpaired_cb); + + queue_destroy(gatt_apps, NULL); + gatt_apps = NULL; + + queue_destroy(gatt_devices, NULL); + gatt_devices = NULL; + + queue_destroy(app_connections, NULL); + app_connections = NULL; + + queue_destroy(listen_apps, NULL); + listen_apps = NULL; + + queue_destroy(services_sdp, NULL); + services_sdp = NULL; + + gatt_db_unref(gatt_db); + gatt_db = NULL; + + bt_crypto_unref(crypto); + crypto = NULL; + + if (le_io) { + g_io_channel_unref(le_io); + le_io = NULL; + } + + if (bredr_io) { + g_io_channel_unref(bredr_io); + bredr_io = NULL; + } + + return false; +} + +void bt_gatt_unregister(void) +{ + DBG(""); + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_GATT); + hal_ipc = NULL; + + queue_destroy(app_connections, destroy_connection); + app_connections = NULL; + + queue_destroy(gatt_apps, destroy_gatt_app); + gatt_apps = NULL; + + queue_destroy(gatt_devices, destroy_device); + gatt_devices = NULL; + + queue_destroy(services_sdp, free_service_sdp_record); + services_sdp = NULL; + + queue_destroy(listen_apps, NULL); + listen_apps = NULL; + + gatt_db_unref(gatt_db); + gatt_db = NULL; + + if (le_io) { + g_io_channel_unref(le_io); + le_io = NULL; + } + + if (bredr_io) { + g_io_channel_unref(bredr_io); + bredr_io = NULL; + } + + if (gap_sdp_handle) { + bt_adapter_remove_record(gap_sdp_handle); + gap_sdp_handle = 0; + } + + if (gatt_sdp_handle) { + bt_adapter_remove_record(gatt_sdp_handle); + gatt_sdp_handle = 0; + } + + if (dis_sdp_handle) { + bt_adapter_remove_record(dis_sdp_handle); + dis_sdp_handle = 0; + } + + bt_crypto_unref(crypto); + crypto = NULL; + + bt_le_unregister(); + bt_unpaired_unregister(gatt_unpaired_cb); +} + +unsigned int bt_gatt_register_app(const char *uuid, gatt_type_t type, + gatt_conn_cb_t func) +{ + struct gatt_app *app; + bt_uuid_t u, u128; + + bt_string_to_uuid(&u, uuid); + bt_uuid_to_uuid128(&u, &u128); + app = register_app((void *) &u128.value.u128, type); + if (!app) + return 0; + + app->func = func; + + return app->id; +} + +bool bt_gatt_unregister_app(unsigned int id) +{ + uint8_t status; + + status = unregister_app(id); + + return status != HAL_STATUS_FAILED; +} + +bool bt_gatt_connect_app(unsigned int id, const bdaddr_t *addr) +{ + uint8_t status; + + status = handle_connect(id, addr, false); + + return status != HAL_STATUS_FAILED; +} + +bool bt_gatt_disconnect_app(unsigned int id, const bdaddr_t *addr) +{ + struct app_connection match; + struct app_connection *conn; + struct gatt_device *device; + struct gatt_app *app; + + app = find_app_by_id(id); + if (!app) + return false; + + device = find_device_by_addr(addr); + if (!device) + return false; + + match.device = device; + match.app = app; + + conn = queue_remove_if(app_connections, + match_connection_by_device_and_app, &match); + if (!conn) + return false; + + destroy_connection(conn); + + return true; +} + +bool bt_gatt_add_autoconnect(unsigned int id, const bdaddr_t *addr) +{ + struct gatt_device *dev; + struct gatt_app *app; + + DBG(""); + + app = find_app_by_id(id); + if (!app) { + error("gatt: App ID=%d not found", id); + return false; + } + + dev = find_device_by_addr(addr); + if (!dev) { + error("gatt: Device not found"); + return false; + } + + /* Take reference of device for auto connect purpose */ + if (queue_isempty(dev->autoconnect_apps)) + device_ref(dev); + + if (!queue_find(dev->autoconnect_apps, NULL, INT_TO_PTR(id))) + return queue_push_head(dev->autoconnect_apps, INT_TO_PTR(id)); + + return true; +} + +void bt_gatt_remove_autoconnect(unsigned int id, const bdaddr_t *addr) +{ + struct gatt_device *dev; + + DBG(""); + + dev = find_device_by_addr(addr); + if (!dev) { + error("gatt: Device not found"); + return; + } + + queue_remove(dev->autoconnect_apps, INT_TO_PTR(id)); + + if (queue_isempty(dev->autoconnect_apps)) + remove_autoconnect_device(dev); +}
diff --git a/android/gatt.h b/android/gatt.h new file mode 100644 index 0000000..3382df9 --- /dev/null +++ b/android/gatt.h
@@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_gatt_register(struct ipc *ipc, const bdaddr_t *addr); +void bt_gatt_unregister(void); + + +typedef enum { + GATT_CLIENT, + GATT_SERVER, +} gatt_type_t; + +typedef void (*gatt_conn_cb_t)(const bdaddr_t *addr, int err, void *attrib); + +unsigned int bt_gatt_register_app(const char *uuid, gatt_type_t type, + gatt_conn_cb_t func); +bool bt_gatt_unregister_app(unsigned int id); + +bool bt_gatt_connect_app(unsigned int id, const bdaddr_t *addr); +bool bt_gatt_disconnect_app(unsigned int id, const bdaddr_t *addr); +bool bt_gatt_set_security(const bdaddr_t *bdaddr, int sec_level); +bool bt_gatt_add_autoconnect(unsigned int id, const bdaddr_t *addr); +void bt_gatt_remove_autoconnect(unsigned int id, const bdaddr_t *addr);
diff --git a/android/hal-a2dp-sink.c b/android/hal-a2dp-sink.c new file mode 100644 index 0000000..a0b7ed1 --- /dev/null +++ b/android/hal-a2dp-sink.c
@@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "hal-ipc.h" + +static const btav_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_conn_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_a2dp_conn_state *ev = buf; + + if (cbs->connection_state_cb) + cbs->connection_state_cb(ev->state, + (bt_bdaddr_t *) (ev->bdaddr)); +} + +static void handle_audio_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_a2dp_audio_state *ev = buf; + + if (cbs->audio_state_cb) + cbs->audio_state_cb(ev->state, (bt_bdaddr_t *)(ev->bdaddr)); +} + +static void handle_audio_config(void *buf, uint16_t len, int fd) +{ + struct hal_ev_a2dp_audio_config *ev = buf; + + if (cbs->audio_config_cb) + cbs->audio_config_cb((bt_bdaddr_t *)(ev->bdaddr), + ev->sample_rate, ev->channel_count); +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_A2DP_CONN_STATE */ + { handle_conn_state, false, sizeof(struct hal_ev_a2dp_conn_state) }, + /* HAL_EV_A2DP_AUDIO_STATE */ + { handle_audio_state, false, sizeof(struct hal_ev_a2dp_audio_state) }, + /* HAL_EV_A2DP_AUDIO_CONFIG */ + { handle_audio_config, false, sizeof(struct hal_ev_a2dp_audio_config) }, +}; + +static bt_status_t a2dp_connect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_a2dp_connect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_A2DP_SINK, HAL_OP_A2DP_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t disconnect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_a2dp_disconnect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_A2DP_SINK, HAL_OP_A2DP_DISCONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t init(btav_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_A2DP_SINK, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_A2DP_SINK; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_A2DP_SINK); + } + + return ret; +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_A2DP_SINK; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_A2DP_SINK); + + cbs = NULL; +} + +static btav_interface_t iface = { + .size = sizeof(iface), + .init = init, + .connect = a2dp_connect, + .disconnect = disconnect, + .cleanup = cleanup +}; + +btav_interface_t *bt_get_a2dp_sink_interface(void) +{ + return &iface; +}
diff --git a/android/hal-a2dp.c b/android/hal-a2dp.c new file mode 100644 index 0000000..f572875 --- /dev/null +++ b/android/hal-a2dp.c
@@ -0,0 +1,165 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "hal-ipc.h" + +static const btav_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_conn_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_a2dp_conn_state *ev = buf; + + if (cbs->connection_state_cb) + cbs->connection_state_cb(ev->state, + (bt_bdaddr_t *) (ev->bdaddr)); +} + +static void handle_audio_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_a2dp_audio_state *ev = buf; + + if (cbs->audio_state_cb) + cbs->audio_state_cb(ev->state, (bt_bdaddr_t *)(ev->bdaddr)); +} + +static void handle_audio_config(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_a2dp_audio_config *ev = buf; + + if (cbs->audio_config_cb) + cbs->audio_config_cb((bt_bdaddr_t *)(ev->bdaddr), + ev->sample_rate, ev->channel_count); +#endif +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_A2DP_CONN_STATE */ + { handle_conn_state, false, sizeof(struct hal_ev_a2dp_conn_state) }, + /* HAL_EV_A2DP_AUDIO_STATE */ + { handle_audio_state, false, sizeof(struct hal_ev_a2dp_audio_state) }, + /* HAL_EV_A2DP_AUDIO_CONFIG */ + { handle_audio_config, false, sizeof(struct hal_ev_a2dp_audio_config) }, +}; + +static bt_status_t a2dp_connect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_a2dp_connect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_A2DP, HAL_OP_A2DP_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t disconnect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_a2dp_disconnect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_A2DP, HAL_OP_A2DP_DISCONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t init(btav_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_A2DP, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_A2DP; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_A2DP); + } + + return ret; +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_A2DP; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_A2DP); + + cbs = NULL; +} + +static btav_interface_t iface = { + .size = sizeof(iface), + .init = init, + .connect = a2dp_connect, + .disconnect = disconnect, + .cleanup = cleanup +}; + +btav_interface_t *bt_get_a2dp_interface(void) +{ + return &iface; +}
diff --git a/android/hal-audio-aptx.c b/android/hal-audio-aptx.c new file mode 100644 index 0000000..a800075 --- /dev/null +++ b/android/hal-audio-aptx.c
@@ -0,0 +1,276 @@ +/* + * Copyright (C) 2014 Tieto Poland + * + * 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. + * + */ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <malloc.h> +#include <dlfcn.h> + +#include "audio-msg.h" +#include "hal-audio.h" +#include "hal-log.h" +#include "profiles/audio/a2dp-codecs.h" + +#define APTX_SO_NAME "libbt-aptx.so" + +struct aptx_data { + a2dp_aptx_t aptx; + + void *enc; +}; + +static const a2dp_aptx_t aptx_presets[] = { + { + .info = { + .vendor_id = APTX_VENDOR_ID, + .codec_id = APTX_CODEC_ID, + }, + .frequency = APTX_SAMPLING_FREQ_44100 | + APTX_SAMPLING_FREQ_48000, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, + { + .info = { + .vendor_id = APTX_VENDOR_ID, + .codec_id = APTX_CODEC_ID, + }, + .frequency = APTX_SAMPLING_FREQ_48000, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, + { + .info = { + .vendor_id = APTX_VENDOR_ID, + .codec_id = APTX_CODEC_ID, + }, + .frequency = APTX_SAMPLING_FREQ_44100, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, +}; + +static void *aptx_handle; +static int aptx_btencsize; +static int (*aptx_init)(void *, short); +static int (*aptx_encode)(void *, void *, void *, void *); + +static bool aptx_load(void) +{ + const char * (*aptx_version)(void); + const char * (*aptx_build)(void); + int (*aptx_sizeofenc)(void); + + aptx_handle = dlopen(APTX_SO_NAME, RTLD_LAZY); + if (!aptx_handle) { + error("APTX: failed to open library %s (%s)", APTX_SO_NAME, + dlerror()); + return false; + } + + aptx_version = dlsym(aptx_handle, "aptxbtenc_version"); + aptx_build = dlsym(aptx_handle, "aptxbtenc_build"); + + if (aptx_version && aptx_build) + info("APTX: using library version %s build %s", aptx_version(), + aptx_build()); + else + warn("APTX: cannot retrieve library version"); + + aptx_sizeofenc = dlsym(aptx_handle, "SizeofAptxbtenc"); + aptx_init = dlsym(aptx_handle, "aptxbtenc_init"); + aptx_encode = dlsym(aptx_handle, "aptxbtenc_encodestereo"); + if (!aptx_sizeofenc || !aptx_init || !aptx_encode) { + error("APTX: failed initialize library"); + dlclose(aptx_handle); + aptx_handle = NULL; + return false; + } + aptx_btencsize = aptx_sizeofenc(); + + info("APTX: codec library initialized (size=%d)", aptx_btencsize); + + return true; +} + +static void aptx_unload(void) +{ + if (!aptx_handle) + return; + + dlclose(aptx_handle); + aptx_handle = NULL; +} + +static int aptx_get_presets(struct audio_preset *preset, size_t *len) +{ + int i; + int count; + size_t new_len = 0; + uint8_t *ptr = (uint8_t *) preset; + size_t preset_size = sizeof(*preset) + sizeof(a2dp_aptx_t); + + DBG(""); + + count = sizeof(aptx_presets) / sizeof(aptx_presets[0]); + + for (i = 0; i < count; i++) { + preset = (struct audio_preset *) ptr; + + if (new_len + preset_size > *len) + break; + + preset->len = sizeof(a2dp_aptx_t); + memcpy(preset->data, &aptx_presets[i], preset->len); + + new_len += preset_size; + ptr += preset_size; + } + + *len = new_len; + + return i; +} + +static bool aptx_codec_init(struct audio_preset *preset, uint16_t payload_len, + void **codec_data) +{ + struct aptx_data *aptx_data; + + DBG(""); + + if (preset->len != sizeof(a2dp_aptx_t)) { + error("APTX: preset size mismatch"); + return false; + } + + aptx_data = malloc(sizeof(*aptx_data)); + if (!aptx_data) + return false; + + memset(aptx_data, 0, sizeof(*aptx_data)); + memcpy(&aptx_data->aptx, preset->data, preset->len); + + aptx_data->enc = calloc(1, aptx_btencsize); + if (!aptx_data->enc) { + error("APTX: failed to create encoder"); + free(aptx_data); + return false; + } + + /* 1 = big-endian, this is what devices are using */ + aptx_init(aptx_data->enc, 1); + + *codec_data = aptx_data; + + return true; +} + +static bool aptx_cleanup(void *codec_data) +{ + struct aptx_data *aptx_data = (struct aptx_data *) codec_data; + + free(aptx_data->enc); + free(codec_data); + + return true; +} + +static bool aptx_get_config(void *codec_data, struct audio_input_config *config) +{ + struct aptx_data *aptx_data = (struct aptx_data *) codec_data; + + config->rate = aptx_data->aptx.frequency & APTX_SAMPLING_FREQ_48000 ? + 48000 : 44100; + config->channels = AUDIO_CHANNEL_OUT_STEREO; + config->format = AUDIO_FORMAT_PCM_16_BIT; + + return true; +} + +static size_t aptx_get_buffer_size(void *codec_data) +{ + /* TODO: return actual value */ + return 0; +} + +static size_t aptx_get_mediapacket_duration(void *codec_data) +{ + /* TODO: return actual value */ + return 0; +} + +static ssize_t aptx_encode_mediapacket(void *codec_data, const uint8_t *buffer, + size_t len, struct media_packet *mp, + size_t mp_data_len, size_t *written) +{ + struct aptx_data *aptx_data = (struct aptx_data *) codec_data; + const int16_t *ptr = (const void *) buffer; + size_t bytes_in = 0; + size_t bytes_out = 0; + + while ((len - bytes_in) >= 16 && (mp_data_len - bytes_out) >= 4) { + int pcm_l[4], pcm_r[4]; + int i; + + for (i = 0; i < 4; i++) { + pcm_l[i] = ptr[0]; + pcm_r[i] = ptr[1]; + ptr += 2; + } + + aptx_encode(aptx_data->enc, pcm_l, pcm_r, &mp->data[bytes_out]); + + bytes_in += 16; + bytes_out += 4; + } + + *written = bytes_out; + + return bytes_in; +} + +static bool aptx_update_qos(void *codec_data, uint8_t op) +{ + /* + * aptX has constant bitrate of 352kbps (with constant 4:1 compression + * ratio) thus QoS is not possible here. + */ + + return false; +} + +static const struct audio_codec codec = { + .type = A2DP_CODEC_VENDOR, + .use_rtp = false, + + .load = aptx_load, + .unload = aptx_unload, + + .get_presets = aptx_get_presets, + + .init = aptx_codec_init, + .cleanup = aptx_cleanup, + .get_config = aptx_get_config, + .get_buffer_size = aptx_get_buffer_size, + .get_mediapacket_duration = aptx_get_mediapacket_duration, + .encode_mediapacket = aptx_encode_mediapacket, + .update_qos = aptx_update_qos, +}; + +const struct audio_codec *codec_aptx(void) +{ + return &codec; +}
diff --git a/android/hal-audio-sbc.c b/android/hal-audio-sbc.c new file mode 100644 index 0000000..7ad3a6a --- /dev/null +++ b/android/hal-audio-sbc.c
@@ -0,0 +1,428 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <string.h> +#include <malloc.h> +#include <stdlib.h> + +#include <sbc/sbc.h> +#include "audio-msg.h" +#include "hal-audio.h" +#include "hal-log.h" +#include "../profiles/audio/a2dp-codecs.h" + +#define MAX_FRAMES_IN_PAYLOAD 15 + +#define SBC_QUALITY_MIN_BITPOOL 33 +#define SBC_QUALITY_STEP 5 + + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +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_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 + +struct media_packet_sbc { + struct media_packet_rtp hdr; + struct rtp_payload payload; + uint8_t data[0]; +}; + +struct sbc_data { + a2dp_sbc_t sbc; + + sbc_t enc; + + uint16_t payload_len; + + size_t in_frame_len; + size_t in_buf_size; + + size_t out_frame_len; + + unsigned frame_duration; + unsigned frames_per_packet; +}; + +static const a2dp_sbc_t sbc_presets[] = { + { + .frequency = SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000, + .channel_mode = SBC_CHANNEL_MODE_MONO | + SBC_CHANNEL_MODE_DUAL_CHANNEL | + SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_JOINT_STEREO, + .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8, + .allocation_method = SBC_ALLOCATION_SNR | + SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16, + .min_bitpool = MIN_BITPOOL, + .max_bitpool = MAX_BITPOOL + }, + { + .frequency = SBC_SAMPLING_FREQ_44100, + .channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO, + .subbands = SBC_SUBBANDS_8, + .allocation_method = SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_16, + .min_bitpool = MIN_BITPOOL, + .max_bitpool = MAX_BITPOOL + }, + { + .frequency = SBC_SAMPLING_FREQ_48000, + .channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO, + .subbands = SBC_SUBBANDS_8, + .allocation_method = SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_16, + .min_bitpool = MIN_BITPOOL, + .max_bitpool = MAX_BITPOOL + }, +}; + +static int sbc_get_presets(struct audio_preset *preset, size_t *len) +{ + int i; + int count; + size_t new_len = 0; + uint8_t *ptr = (uint8_t *) preset; + size_t preset_size = sizeof(*preset) + sizeof(a2dp_sbc_t); + + count = sizeof(sbc_presets) / sizeof(sbc_presets[0]); + + for (i = 0; i < count; i++) { + preset = (struct audio_preset *) ptr; + + if (new_len + preset_size > *len) + break; + + preset->len = sizeof(a2dp_sbc_t); + memcpy(preset->data, &sbc_presets[i], preset->len); + + new_len += preset_size; + ptr += preset_size; + } + + *len = new_len; + + return i; +} + +static int sbc_freq2int(uint8_t freq) +{ + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + return 16000; + case SBC_SAMPLING_FREQ_32000: + return 32000; + case SBC_SAMPLING_FREQ_44100: + return 44100; + case SBC_SAMPLING_FREQ_48000: + return 48000; + default: + return 0; + } +} + +static const char *sbc_mode2str(uint8_t mode) +{ + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + return "Mono"; + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return "DualChannel"; + case SBC_CHANNEL_MODE_STEREO: + return "Stereo"; + case SBC_CHANNEL_MODE_JOINT_STEREO: + return "JointStereo"; + default: + return "(unknown)"; + } +} + +static int sbc_blocks2int(uint8_t blocks) +{ + switch (blocks) { + case SBC_BLOCK_LENGTH_4: + return 4; + case SBC_BLOCK_LENGTH_8: + return 8; + case SBC_BLOCK_LENGTH_12: + return 12; + case SBC_BLOCK_LENGTH_16: + return 16; + default: + return 0; + } +} + +static int sbc_subbands2int(uint8_t subbands) +{ + switch (subbands) { + case SBC_SUBBANDS_4: + return 4; + case SBC_SUBBANDS_8: + return 8; + default: + return 0; + } +} + +static const char *sbc_allocation2str(uint8_t allocation) +{ + switch (allocation) { + case SBC_ALLOCATION_SNR: + return "SNR"; + case SBC_ALLOCATION_LOUDNESS: + return "Loudness"; + default: + return "(unknown)"; + } +} + +static void sbc_init_encoder(struct sbc_data *sbc_data) +{ + a2dp_sbc_t *in = &sbc_data->sbc; + sbc_t *out = &sbc_data->enc; + + sbc_init_a2dp(out, 0L, in, sizeof(*in)); + + out->endian = SBC_LE; + out->bitpool = in->max_bitpool; + + DBG("frequency=%d channel_mode=%s block_length=%d subbands=%d allocation=%s bitpool=%d-%d", + sbc_freq2int(in->frequency), + sbc_mode2str(in->channel_mode), + sbc_blocks2int(in->block_length), + sbc_subbands2int(in->subbands), + sbc_allocation2str(in->allocation_method), + in->min_bitpool, in->max_bitpool); +} + +static void sbc_codec_calculate(struct sbc_data *sbc_data) +{ + size_t in_frame_len; + size_t out_frame_len; + size_t num_frames; + + in_frame_len = sbc_get_codesize(&sbc_data->enc); + out_frame_len = sbc_get_frame_length(&sbc_data->enc); + num_frames = sbc_data->payload_len / out_frame_len; + + if (num_frames > MAX_FRAMES_IN_PAYLOAD) + num_frames = MAX_FRAMES_IN_PAYLOAD; + + sbc_data->in_frame_len = in_frame_len; + sbc_data->in_buf_size = num_frames * in_frame_len; + + sbc_data->out_frame_len = out_frame_len; + + sbc_data->frame_duration = sbc_get_frame_duration(&sbc_data->enc); + sbc_data->frames_per_packet = num_frames; + + DBG("in_frame_len=%zu out_frame_len=%zu frames_per_packet=%zu", + in_frame_len, out_frame_len, num_frames); +} + +static bool sbc_codec_init(struct audio_preset *preset, uint16_t payload_len, + void **codec_data) +{ + struct sbc_data *sbc_data; + + if (preset->len != sizeof(a2dp_sbc_t)) { + error("SBC: preset size mismatch"); + return false; + } + + sbc_data = calloc(sizeof(struct sbc_data), 1); + if (!sbc_data) + return false; + + memcpy(&sbc_data->sbc, preset->data, preset->len); + + sbc_init_encoder(sbc_data); + + sbc_data->payload_len = payload_len; + + sbc_codec_calculate(sbc_data); + + *codec_data = sbc_data; + + return true; +} + +static bool sbc_cleanup(void *codec_data) +{ + struct sbc_data *sbc_data = (struct sbc_data *) codec_data; + + sbc_finish(&sbc_data->enc); + free(codec_data); + + return true; +} + +static bool sbc_get_config(void *codec_data, struct audio_input_config *config) +{ + struct sbc_data *sbc_data = (struct sbc_data *) codec_data; + + switch (sbc_data->sbc.frequency) { + case SBC_SAMPLING_FREQ_16000: + config->rate = 16000; + break; + case SBC_SAMPLING_FREQ_32000: + config->rate = 32000; + break; + case SBC_SAMPLING_FREQ_44100: + config->rate = 44100; + break; + case SBC_SAMPLING_FREQ_48000: + config->rate = 48000; + break; + default: + return false; + } + config->channels = sbc_data->sbc.channel_mode == SBC_CHANNEL_MODE_MONO ? + AUDIO_CHANNEL_OUT_MONO : + AUDIO_CHANNEL_OUT_STEREO; + config->format = AUDIO_FORMAT_PCM_16_BIT; + + return true; +} + +static size_t sbc_get_buffer_size(void *codec_data) +{ + struct sbc_data *sbc_data = (struct sbc_data *) codec_data; + + return sbc_data->in_buf_size; +} + +static size_t sbc_get_mediapacket_duration(void *codec_data) +{ + struct sbc_data *sbc_data = (struct sbc_data *) codec_data; + + return sbc_data->frame_duration * sbc_data->frames_per_packet; +} + +static ssize_t sbc_encode_mediapacket(void *codec_data, const uint8_t *buffer, + size_t len, struct media_packet *mp, + size_t mp_data_len, size_t *written) +{ + struct sbc_data *sbc_data = (struct sbc_data *) codec_data; + struct media_packet_sbc *mp_sbc = (struct media_packet_sbc *) mp; + size_t consumed = 0; + size_t encoded = 0; + uint8_t frame_count = 0; + + mp_data_len -= sizeof(mp_sbc->payload); + + while (len - consumed >= sbc_data->in_frame_len && + mp_data_len - encoded >= sbc_data->out_frame_len && + frame_count < sbc_data->frames_per_packet) { + ssize_t read; + ssize_t written = 0; + + read = sbc_encode(&sbc_data->enc, buffer + consumed, + sbc_data->in_frame_len, mp_sbc->data + encoded, + mp_data_len - encoded, &written); + + if (read < 0) { + error("SBC: failed to encode block at frame %d (%zd)", + frame_count, read); + break; + } + + frame_count++; + consumed += read; + encoded += written; + } + + *written = encoded + sizeof(mp_sbc->payload); + mp_sbc->payload.frame_count = frame_count; + + return consumed; +} + +static bool sbc_update_qos(void *codec_data, uint8_t op) +{ + struct sbc_data *sbc_data = (struct sbc_data *) codec_data; + uint8_t curr_bitpool = sbc_data->enc.bitpool; + uint8_t new_bitpool = curr_bitpool; + + switch (op) { + case QOS_POLICY_DEFAULT: + new_bitpool = sbc_data->sbc.max_bitpool; + break; + + case QOS_POLICY_DECREASE: + if (curr_bitpool > SBC_QUALITY_MIN_BITPOOL) { + new_bitpool = curr_bitpool - SBC_QUALITY_STEP; + if (new_bitpool < SBC_QUALITY_MIN_BITPOOL) + new_bitpool = SBC_QUALITY_MIN_BITPOOL; + } + break; + } + + if (new_bitpool == curr_bitpool) + return false; + + sbc_data->enc.bitpool = new_bitpool; + + sbc_codec_calculate(sbc_data); + + info("SBC: bitpool changed: %d -> %d", curr_bitpool, new_bitpool); + + return true; +} + +static const struct audio_codec codec = { + .type = A2DP_CODEC_SBC, + .use_rtp = true, + + .get_presets = sbc_get_presets, + + .init = sbc_codec_init, + .cleanup = sbc_cleanup, + .get_config = sbc_get_config, + .get_buffer_size = sbc_get_buffer_size, + .get_mediapacket_duration = sbc_get_mediapacket_duration, + .encode_mediapacket = sbc_encode_mediapacket, + .update_qos = sbc_update_qos, +}; + +const struct audio_codec *codec_sbc(void) +{ + return &codec; +}
diff --git a/android/hal-audio.c b/android/hal-audio.c new file mode 100644 index 0000000..207101f --- /dev/null +++ b/android/hal-audio.c
@@ -0,0 +1,1641 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <errno.h> +#include <pthread.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <fcntl.h> + +#include <hardware/audio.h> +#include <hardware/hardware.h> + +#include "audio-msg.h" +#include "ipc-common.h" +#include "hal-log.h" +#include "hal-msg.h" +#include "hal-audio.h" +#include "hal-utils.h" +#include "hal.h" + +#define FIXED_A2DP_PLAYBACK_LATENCY_MS 25 + +#define FIXED_BUFFER_SIZE (20 * 512) + +#define MAX_DELAY 100000 /* 100ms */ + +static const uint8_t a2dp_src_uuid[] = { + 0x00, 0x00, 0x11, 0x0a, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb }; + +static int listen_sk = -1; +static int audio_sk = -1; + +static pthread_t ipc_th = 0; +static pthread_mutex_t sk_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void timespec_add(struct timespec *base, uint64_t time_us, + struct timespec *res) +{ + res->tv_sec = base->tv_sec + time_us / 1000000; + res->tv_nsec = base->tv_nsec + (time_us % 1000000) * 1000; + + if (res->tv_nsec >= 1000000000) { + res->tv_sec++; + res->tv_nsec -= 1000000000; + } +} + +static void timespec_diff(struct timespec *a, struct timespec *b, + struct timespec *res) +{ + res->tv_sec = a->tv_sec - b->tv_sec; + res->tv_nsec = a->tv_nsec - b->tv_nsec; + + if (res->tv_nsec < 0) { + res->tv_sec--; + res->tv_nsec += 1000000000; /* 1sec */ + } +} + +static uint64_t timespec_diff_us(struct timespec *a, struct timespec *b) +{ + struct timespec res; + + timespec_diff(a, b, &res); + + return res.tv_sec * 1000000ll + res.tv_nsec / 1000ll; +} + +#if defined(ANDROID) +/* + * Bionic does not have clock_nanosleep() prototype in time.h even though + * it provides its implementation. + */ +extern int clock_nanosleep(clockid_t clock_id, int flags, + const struct timespec *request, + struct timespec *remain); +#endif + +static struct { + const audio_codec_get_t get_codec; + bool loaded; +} audio_codecs[] = { + { .get_codec = codec_aptx, .loaded = false }, + { .get_codec = codec_sbc, .loaded = false }, +}; + +#define NUM_CODECS (sizeof(audio_codecs) / sizeof(audio_codecs[0])) + +#define MAX_AUDIO_ENDPOINTS NUM_CODECS + +struct audio_endpoint { + uint8_t id; + const struct audio_codec *codec; + void *codec_data; + int fd; + + struct media_packet *mp; + size_t mp_data_len; + + uint16_t seq; + uint32_t samples; + struct timespec start; + + bool resync; +}; + +static struct audio_endpoint audio_endpoints[MAX_AUDIO_ENDPOINTS]; + +enum a2dp_state_t { + AUDIO_A2DP_STATE_NONE, + AUDIO_A2DP_STATE_STANDBY, + AUDIO_A2DP_STATE_SUSPENDED, + AUDIO_A2DP_STATE_STARTED +}; + +struct a2dp_stream_out { + struct audio_stream_out stream; + + struct audio_endpoint *ep; + enum a2dp_state_t audio_state; + struct audio_input_config cfg; + + uint8_t *downmix_buf; +}; + +struct a2dp_audio_dev { + struct audio_hw_device dev; + struct a2dp_stream_out *out; +}; + +static int audio_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len, + void *param, size_t *rsp_len, void *rsp, int *fd) +{ + ssize_t ret; + struct msghdr msg; + struct iovec iv[2]; + struct ipc_hdr cmd; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + struct ipc_status s; + size_t s_len = sizeof(s); + + pthread_mutex_lock(&sk_mutex); + + if (audio_sk < 0) { + error("audio: Invalid cmd socket passed to audio_ipc_cmd"); + goto failed; + } + + if (!rsp || !rsp_len) { + memset(&s, 0, s_len); + rsp_len = &s_len; + rsp = &s; + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmd, 0, sizeof(cmd)); + + cmd.service_id = service_id; + cmd.opcode = opcode; + cmd.len = len; + + iv[0].iov_base = &cmd; + iv[0].iov_len = sizeof(cmd); + + iv[1].iov_base = param; + iv[1].iov_len = len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + ret = sendmsg(audio_sk, &msg, 0); + if (ret < 0) { + error("audio: Sending command failed:%s", strerror(errno)); + goto failed; + } + + /* socket was shutdown */ + if (ret == 0) { + error("audio: Command socket closed"); + goto failed; + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmd, 0, sizeof(cmd)); + + iv[0].iov_base = &cmd; + iv[0].iov_len = sizeof(cmd); + + iv[1].iov_base = rsp; + iv[1].iov_len = *rsp_len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + if (fd) { + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + } + + ret = recvmsg(audio_sk, &msg, 0); + if (ret < 0) { + error("audio: Receiving command response failed:%s", + strerror(errno)); + goto failed; + } + + if (ret < (ssize_t) sizeof(cmd)) { + error("audio: Too small response received(%zd bytes)", ret); + goto failed; + } + + if (cmd.service_id != service_id) { + error("audio: Invalid service id (%u vs %u)", cmd.service_id, + service_id); + goto failed; + } + + if (ret != (ssize_t) (sizeof(cmd) + cmd.len)) { + error("audio: Malformed response received(%zd bytes)", ret); + goto failed; + } + + if (cmd.opcode != opcode && cmd.opcode != AUDIO_OP_STATUS) { + error("audio: Invalid opcode received (%u vs %u)", + cmd.opcode, opcode); + goto failed; + } + + if (cmd.opcode == AUDIO_OP_STATUS) { + struct ipc_status *s = rsp; + + if (sizeof(*s) != cmd.len) { + error("audio: Invalid status length"); + goto failed; + } + + if (s->code == AUDIO_STATUS_SUCCESS) { + error("audio: Invalid success status response"); + goto failed; + } + + pthread_mutex_unlock(&sk_mutex); + + return s->code; + } + + pthread_mutex_unlock(&sk_mutex); + + /* Receive auxiliary data in msg */ + if (fd) { + struct cmsghdr *cmsg; + + *fd = -1; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); + break; + } + } + + if (*fd < 0) + goto failed; + } + + *rsp_len = cmd.len; + + return AUDIO_STATUS_SUCCESS; + +failed: + /* Some serious issue happen on IPC - recover */ + shutdown(audio_sk, SHUT_RDWR); + pthread_mutex_unlock(&sk_mutex); + + return AUDIO_STATUS_FAILED; +} + +static int ipc_open_cmd(const struct audio_codec *codec) +{ + uint8_t buf[BLUEZ_AUDIO_MTU]; + struct audio_cmd_open *cmd = (struct audio_cmd_open *) buf; + struct audio_rsp_open rsp; + size_t cmd_len = sizeof(buf) - sizeof(*cmd); + size_t rsp_len = sizeof(rsp); + int result; + + DBG(""); + + memcpy(cmd->uuid, a2dp_src_uuid, sizeof(a2dp_src_uuid)); + + cmd->codec = codec->type; + cmd->presets = codec->get_presets(cmd->preset, &cmd_len); + + cmd_len += sizeof(*cmd); + + result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_OPEN, cmd_len, cmd, + &rsp_len, &rsp, NULL); + + if (result != AUDIO_STATUS_SUCCESS) + return 0; + + return rsp.id; +} + +static int ipc_close_cmd(uint8_t endpoint_id) +{ + struct audio_cmd_close cmd; + int result; + + DBG(""); + + cmd.id = endpoint_id; + + result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_CLOSE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + return result; +} + +static int ipc_open_stream_cmd(uint8_t *endpoint_id, uint16_t *mtu, int *fd, + struct audio_preset **caps) +{ + char buf[BLUEZ_AUDIO_MTU]; + struct audio_cmd_open_stream cmd; + struct audio_rsp_open_stream *rsp = + (struct audio_rsp_open_stream *) &buf; + size_t rsp_len = sizeof(buf); + int result; + + DBG(""); + + if (!caps) + return AUDIO_STATUS_FAILED; + + cmd.id = *endpoint_id; + + result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, + sizeof(cmd), &cmd, &rsp_len, rsp, fd); + if (result == AUDIO_STATUS_SUCCESS) { + size_t buf_len = sizeof(struct audio_preset) + + rsp->preset[0].len; + *endpoint_id = rsp->id; + *mtu = rsp->mtu; + *caps = malloc(buf_len); + memcpy(*caps, &rsp->preset, buf_len); + } else { + *caps = NULL; + } + + return result; +} + +static int ipc_close_stream_cmd(uint8_t endpoint_id) +{ + struct audio_cmd_close_stream cmd; + int result; + + DBG(""); + + cmd.id = endpoint_id; + + result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_CLOSE_STREAM, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + return result; +} + +static int ipc_resume_stream_cmd(uint8_t endpoint_id) +{ + struct audio_cmd_resume_stream cmd; + int result; + + DBG(""); + + cmd.id = endpoint_id; + + result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_RESUME_STREAM, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + return result; +} + +static int ipc_suspend_stream_cmd(uint8_t endpoint_id) +{ + struct audio_cmd_suspend_stream cmd; + int result; + + DBG(""); + + cmd.id = endpoint_id; + + result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_SUSPEND_STREAM, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + return result; +} + +struct register_state { + struct audio_endpoint *ep; + bool error; +}; + +static void register_endpoint(const struct audio_codec *codec, + struct register_state *state) +{ + struct audio_endpoint *ep = state->ep; + + /* don't even try to register more endpoints if one failed */ + if (state->error) + return; + + ep->id = ipc_open_cmd(codec); + + if (!ep->id) { + state->error = true; + error("Failed to register endpoint"); + return; + } + + ep->codec = codec; + ep->codec_data = NULL; + ep->fd = -1; + + state->ep++; +} + +static int register_endpoints(void) +{ + struct register_state state; + unsigned int i; + + state.ep = &audio_endpoints[0]; + state.error = false; + + for (i = 0; i < NUM_CODECS; i++) { + const struct audio_codec *codec = audio_codecs[i].get_codec(); + + if (!audio_codecs[i].loaded) + continue; + + register_endpoint(codec, &state); + } + + return state.error ? AUDIO_STATUS_FAILED : AUDIO_STATUS_SUCCESS; +} + +static void unregister_endpoints(void) +{ + size_t i; + + for (i = 0; i < MAX_AUDIO_ENDPOINTS; i++) { + struct audio_endpoint *ep = &audio_endpoints[i]; + + if (ep->id) { + ipc_close_cmd(ep->id); + memset(ep, 0, sizeof(*ep)); + } + } +} + +static bool open_endpoint(struct audio_endpoint **epp, + struct audio_input_config *cfg) +{ + struct audio_preset *preset; + struct audio_endpoint *ep = *epp; + const struct audio_codec *codec; + uint16_t mtu; + uint16_t payload_len; + int fd; + size_t i; + uint8_t ep_id = 0; + + if (ep) + ep_id = ep->id; + + if (ipc_open_stream_cmd(&ep_id, &mtu, &fd, &preset) != + AUDIO_STATUS_SUCCESS) + return false; + + DBG("ep_id=%d mtu=%u", ep_id, mtu); + + for (i = 0; i < MAX_AUDIO_ENDPOINTS; i++) + if (audio_endpoints[i].id == ep_id) { + ep = &audio_endpoints[i]; + break; + } + + if (!ep) { + error("Cound not find opened endpoint"); + goto failed; + } + + *epp = ep; + + payload_len = mtu; + if (ep->codec->use_rtp) + payload_len -= sizeof(struct rtp_header); + + ep->fd = fd; + + codec = ep->codec; + codec->init(preset, payload_len, &ep->codec_data); + codec->get_config(ep->codec_data, cfg); + + ep->mp = calloc(mtu, 1); + if (!ep->mp) + goto failed; + + if (ep->codec->use_rtp) { + struct media_packet_rtp *mp_rtp = + (struct media_packet_rtp *) ep->mp; + mp_rtp->hdr.v = 2; + mp_rtp->hdr.pt = 0x60; + mp_rtp->hdr.ssrc = htonl(1); + } + + ep->mp_data_len = payload_len; + + free(preset); + + return true; + +failed: + close(fd); + free(preset); + + return false; +} + +static void close_endpoint(struct audio_endpoint *ep) +{ + ipc_close_stream_cmd(ep->id); + if (ep->fd >= 0) { + close(ep->fd); + ep->fd = -1; + } + + free(ep->mp); + + ep->codec->cleanup(ep->codec_data); + ep->codec_data = NULL; +} + +static bool resume_endpoint(struct audio_endpoint *ep) +{ + if (ipc_resume_stream_cmd(ep->id) != AUDIO_STATUS_SUCCESS) + return false; + + ep->samples = 0; + ep->resync = false; + + ep->codec->update_qos(ep->codec_data, QOS_POLICY_DEFAULT); + + return true; +} + +static void downmix_to_mono(struct a2dp_stream_out *out, const uint8_t *buffer, + size_t bytes) +{ + const int16_t *input = (const void *) buffer; + int16_t *output = (void *) out->downmix_buf; + size_t i, frames; + + /* PCM 16bit stereo */ + frames = bytes / (2 * sizeof(int16_t)); + + for (i = 0; i < frames; i++) { + int16_t l = get_le16(&input[i * 2]); + int16_t r = get_le16(&input[i * 2 + 1]); + + put_le16((l + r) / 2, &output[i]); + } +} + +static bool wait_for_endpoint(struct audio_endpoint *ep, bool *writable) +{ + int ret; + + while (true) { + struct pollfd pollfd; + + pollfd.fd = ep->fd; + pollfd.events = POLLOUT; + pollfd.revents = 0; + + ret = poll(&pollfd, 1, 500); + + if (ret >= 0) { + *writable = !!(pollfd.revents & POLLOUT); + break; + } + + if (errno != EINTR) { + ret = errno; + error("poll failed (%d)", ret); + return false; + } + } + + return true; +} + +static bool write_to_endpoint(struct audio_endpoint *ep, size_t bytes) +{ + struct media_packet *mp = (struct media_packet *) ep->mp; + int ret; + + while (true) { + ret = write(ep->fd, mp, bytes); + + if (ret >= 0) + break; + + /* + * this should not happen so let's issue warning, but do not + * fail, we can try to write next packet + */ + if (errno == EAGAIN) { + ret = errno; + warn("write failed (%d)", ret); + break; + } + + if (errno != EINTR) { + ret = errno; + error("write failed (%d)", ret); + return false; + } + } + + return true; +} + +static bool write_data(struct a2dp_stream_out *out, const void *buffer, + size_t bytes) +{ + struct audio_endpoint *ep = out->ep; + struct media_packet *mp = (struct media_packet *) ep->mp; + struct media_packet_rtp *mp_rtp = (struct media_packet_rtp *) ep->mp; + size_t free_space = ep->mp_data_len; + size_t consumed = 0; + + while (consumed < bytes) { + size_t written = 0; + ssize_t read; + uint32_t samples; + int ret; + struct timespec current; + uint64_t audio_sent, audio_passed; + bool do_write = false; + + /* + * prepare media packet in advance so we don't waste time after + * wakeup + */ + if (ep->codec->use_rtp) { + mp_rtp->hdr.sequence_number = htons(ep->seq++); + mp_rtp->hdr.timestamp = htonl(ep->samples); + } + read = ep->codec->encode_mediapacket(ep->codec_data, + buffer + consumed, + bytes - consumed, mp, + free_space, &written); + + /* + * not much we can do here, let's just ignore remaining + * data and continue + */ + if (read <= 0) + return true; + + /* calculate where are we and where we should be */ + clock_gettime(CLOCK_MONOTONIC, ¤t); + if (!ep->samples) + memcpy(&ep->start, ¤t, sizeof(ep->start)); + audio_sent = ep->samples * 1000000ll / out->cfg.rate; + audio_passed = timespec_diff_us(¤t, &ep->start); + + /* + * if we're ahead of stream then wait for next write point, + * if we're lagging more than 100ms then stop writing and just + * skip data until we're back in sync + */ + if (audio_sent > audio_passed) { + struct timespec anchor; + + ep->resync = false; + + timespec_add(&ep->start, audio_sent, &anchor); + + while (true) { + ret = clock_nanosleep(CLOCK_MONOTONIC, + TIMER_ABSTIME, &anchor, + NULL); + + if (!ret) + break; + + if (ret != EINTR) { + error("clock_nanosleep failed (%d)", + ret); + return false; + } + } + } else if (!ep->resync) { + uint64_t diff = audio_passed - audio_sent; + + if (diff > MAX_DELAY) { + warn("lag is %jums, resyncing", diff / 1000); + + ep->codec->update_qos(ep->codec_data, + QOS_POLICY_DECREASE); + ep->resync = true; + } + } + + /* we send data only in case codec encoded some data, i.e. some + * codecs do internal buffering and output data only if full + * frame can be encoded + * in resync mode we'll just drop mediapackets + */ + if (written > 0 && !ep->resync) { + /* wait some time for socket to be ready for write, + * but we'll just skip writing data if timeout occurs + */ + if (!wait_for_endpoint(ep, &do_write)) + return false; + + if (do_write) { + if (ep->codec->use_rtp) + written += sizeof(struct rtp_header); + + if (!write_to_endpoint(ep, written)) + return false; + } + } + + /* + * AudioFlinger provides 16bit PCM, so sample size is 2 bytes + * multiplied by number of channels. Number of channels is + * simply number of bits set in channels mask. + */ + samples = read / (2 * popcount(out->cfg.channels)); + ep->samples += samples; + consumed += read; + } + + return true; +} + +static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, + size_t bytes) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + const void *in_buf = buffer; + size_t in_len = bytes; + + /* just return in case we're closing */ + if (out->audio_state == AUDIO_A2DP_STATE_NONE) + return -1; + + /* We can auto-start only from standby */ + if (out->audio_state == AUDIO_A2DP_STATE_STANDBY) { + DBG("stream in standby, auto-start"); + + if (!resume_endpoint(out->ep)) + return -1; + + out->audio_state = AUDIO_A2DP_STATE_STARTED; + } + + if (out->audio_state != AUDIO_A2DP_STATE_STARTED) { + error("audio: stream not started"); + return -1; + } + + if (out->ep->fd < 0) { + error("audio: no transport socket"); + return -1; + } + + /* + * currently Android audioflinger is not able to provide mono stream on + * A2DP output so down mixing needs to be done in hal-audio plugin. + * + * for reference see + * AudioFlinger::PlaybackThread::readOutputParameters() + * frameworks/av/services/audioflinger/Threads.cpp:1631 + */ + if (out->cfg.channels == AUDIO_CHANNEL_OUT_MONO) { + if (!out->downmix_buf) { + error("audio: downmix buffer not initialized"); + return -1; + } + + downmix_to_mono(out, buffer, bytes); + + in_buf = out->downmix_buf; + in_len = bytes / 2; + } + + if (!write_data(out, in_buf, in_len)) + return -1; + + return bytes; +} + +static uint32_t out_get_sample_rate(const struct audio_stream *stream) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + + DBG(""); + + return out->cfg.rate; +} + +static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + + DBG(""); + + if (rate != out->cfg.rate) { + warn("audio: cannot set sample rate to %d", rate); + return -1; + } + + return 0; +} + +static size_t out_get_buffer_size(const struct audio_stream *stream) +{ + DBG(""); + + /* + * We should return proper buffer size calculated by codec (so each + * input buffer is encoded into single media packed) but this does not + * work well with AudioFlinger and causes problems. For this reason we + * use magic value here and out_write code takes care of splitting + * input buffer into multiple media packets. + */ + return FIXED_BUFFER_SIZE; +} + +static uint32_t out_get_channels(const struct audio_stream *stream) +{ + DBG(""); + + /* + * AudioFlinger can only provide stereo stream, so we return it here and + * later we'll downmix this to mono in case codec requires it + */ + + return AUDIO_CHANNEL_OUT_STEREO; +} + +static audio_format_t out_get_format(const struct audio_stream *stream) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + + DBG(""); + + return out->cfg.format; +} + +static int out_set_format(struct audio_stream *stream, audio_format_t format) +{ + DBG(""); + return -ENOSYS; +} + +static int out_standby(struct audio_stream *stream) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + + DBG(""); + + if (out->audio_state == AUDIO_A2DP_STATE_STARTED) { + if (ipc_suspend_stream_cmd(out->ep->id) != AUDIO_STATUS_SUCCESS) + return -1; + out->audio_state = AUDIO_A2DP_STATE_STANDBY; + } + + return 0; +} + +static int out_dump(const struct audio_stream *stream, int fd) +{ + DBG(""); + return -ENOSYS; +} + +static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + char *kvpair; + char *str; + char *saveptr; + bool enter_suspend = false; + bool exit_suspend = false; + + DBG("%s", kvpairs); + + str = strdup(kvpairs); + if (!str) + return -ENOMEM; + + kvpair = strtok_r(str, ";", &saveptr); + + for (; kvpair && *kvpair; kvpair = strtok_r(NULL, ";", &saveptr)) { + char *keyval; + + keyval = strchr(kvpair, '='); + if (!keyval) + continue; + + *keyval = '\0'; + keyval++; + + if (!strcmp(kvpair, "closing")) { + if (!strcmp(keyval, "true")) + out->audio_state = AUDIO_A2DP_STATE_NONE; + } else if (!strcmp(kvpair, "A2dpSuspended")) { + if (!strcmp(keyval, "true")) + enter_suspend = true; + else + exit_suspend = true; + } + } + + free(str); + + if (enter_suspend && out->audio_state == AUDIO_A2DP_STATE_STARTED) { + if (ipc_suspend_stream_cmd(out->ep->id) != AUDIO_STATUS_SUCCESS) + return -1; + out->audio_state = AUDIO_A2DP_STATE_SUSPENDED; + } + + if (exit_suspend && out->audio_state == AUDIO_A2DP_STATE_SUSPENDED) + out->audio_state = AUDIO_A2DP_STATE_STANDBY; + + return 0; +} + +static char *out_get_parameters(const struct audio_stream *stream, + const char *keys) +{ + DBG(""); + return strdup(""); +} + +static uint32_t out_get_latency(const struct audio_stream_out *stream) +{ + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + struct audio_endpoint *ep = out->ep; + size_t pkt_duration; + + DBG(""); + + pkt_duration = ep->codec->get_mediapacket_duration(ep->codec_data); + + return FIXED_A2DP_PLAYBACK_LATENCY_MS + pkt_duration / 1000; +} + +static int out_set_volume(struct audio_stream_out *stream, float left, + float right) +{ + DBG(""); + /* volume controlled in audioflinger mixer (digital) */ + return -ENOSYS; +} + +static int out_get_render_position(const struct audio_stream_out *stream, + uint32_t *dsp_frames) +{ + DBG(""); + return -ENOSYS; +} + +static int out_add_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + return -ENOSYS; +} + +static int out_remove_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + return -ENOSYS; +} + +static uint32_t in_get_sample_rate(const struct audio_stream *stream) +{ + DBG(""); + return -ENOSYS; +} + +static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) +{ + DBG(""); + return -ENOSYS; +} + +static size_t in_get_buffer_size(const struct audio_stream *stream) +{ + DBG(""); + return -ENOSYS; +} + +static uint32_t in_get_channels(const struct audio_stream *stream) +{ + DBG(""); + return -ENOSYS; +} + +static audio_format_t in_get_format(const struct audio_stream *stream) +{ + DBG(""); + return -ENOSYS; +} + +static int in_set_format(struct audio_stream *stream, audio_format_t format) +{ + DBG(""); + return -ENOSYS; +} + +static int in_standby(struct audio_stream *stream) +{ + DBG(""); + return -ENOSYS; +} + +static int in_dump(const struct audio_stream *stream, int fd) +{ + DBG(""); + return -ENOSYS; +} + +static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) +{ + DBG(""); + return -ENOSYS; +} + +static char *in_get_parameters(const struct audio_stream *stream, + const char *keys) +{ + DBG(""); + return strdup(""); +} + +static int in_set_gain(struct audio_stream_in *stream, float gain) +{ + DBG(""); + return -ENOSYS; +} + +static ssize_t in_read(struct audio_stream_in *stream, void *buffer, + size_t bytes) +{ + DBG(""); + return -ENOSYS; +} + +static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) +{ + DBG(""); + return -ENOSYS; +} + +static int in_add_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + return -ENOSYS; +} + +static int in_remove_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + return -ENOSYS; +} + +static int audio_open_output_stream_real(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out, + const char *address) +{ + struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *) dev; + struct a2dp_stream_out *out; + + out = calloc(1, sizeof(struct a2dp_stream_out)); + if (!out) + return -ENOMEM; + + DBG(""); + + out->stream.common.get_sample_rate = out_get_sample_rate; + out->stream.common.set_sample_rate = out_set_sample_rate; + out->stream.common.get_buffer_size = out_get_buffer_size; + out->stream.common.get_channels = out_get_channels; + out->stream.common.get_format = out_get_format; + out->stream.common.set_format = out_set_format; + out->stream.common.standby = out_standby; + out->stream.common.dump = out_dump; + out->stream.common.set_parameters = out_set_parameters; + out->stream.common.get_parameters = out_get_parameters; + out->stream.common.add_audio_effect = out_add_audio_effect; + out->stream.common.remove_audio_effect = out_remove_audio_effect; + out->stream.get_latency = out_get_latency; + out->stream.set_volume = out_set_volume; + out->stream.write = out_write; + out->stream.get_render_position = out_get_render_position; + + /* We want to autoselect opened endpoint */ + out->ep = NULL; + + if (!open_endpoint(&out->ep, &out->cfg)) + goto fail; + + DBG("rate=%d channels=%d format=%d", out->cfg.rate, + out->cfg.channels, out->cfg.format); + + if (out->cfg.channels == AUDIO_CHANNEL_OUT_MONO) { + out->downmix_buf = malloc(FIXED_BUFFER_SIZE / 2); + if (!out->downmix_buf) + goto fail; + } + + *stream_out = &out->stream; + a2dp_dev->out = out; + + out->audio_state = AUDIO_A2DP_STATE_STANDBY; + + return 0; + +fail: + error("audio: cannot open output stream"); + free(out); + *stream_out = NULL; + return -EIO; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int audio_open_output_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out, + const char *address) +{ + return audio_open_output_stream_real(dev, handle, devices, flags, + config, stream_out, address); +} +#else +static int audio_open_output_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out) +{ + return audio_open_output_stream_real(dev, handle, devices, flags, + config, stream_out, NULL); +} +#endif + +static void audio_close_output_stream(struct audio_hw_device *dev, + struct audio_stream_out *stream) +{ + struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *) dev; + struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; + + DBG(""); + + close_endpoint(a2dp_dev->out->ep); + + free(out->downmix_buf); + + free(stream); + a2dp_dev->out = NULL; +} + +static int audio_set_parameters(struct audio_hw_device *dev, + const char *kvpairs) +{ + struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *) dev; + struct a2dp_stream_out *out = a2dp_dev->out; + + DBG(""); + + if (!out) + return 0; + + return out->stream.common.set_parameters((struct audio_stream *) out, + kvpairs); +} + +static char *audio_get_parameters(const struct audio_hw_device *dev, + const char *keys) +{ + DBG(""); + return strdup(""); +} + +static int audio_init_check(const struct audio_hw_device *dev) +{ + DBG(""); + return 0; +} + +static int audio_set_voice_volume(struct audio_hw_device *dev, float volume) +{ + DBG(""); + return -ENOSYS; +} + +static int audio_set_master_volume(struct audio_hw_device *dev, float volume) +{ + DBG(""); + return -ENOSYS; +} + +static int audio_set_mode(struct audio_hw_device *dev, int mode) +{ + DBG(""); + return -ENOSYS; +} + +static int audio_set_mic_mute(struct audio_hw_device *dev, bool state) +{ + DBG(""); + return -ENOSYS; +} + +static int audio_get_mic_mute(const struct audio_hw_device *dev, bool *state) +{ + DBG(""); + return -ENOSYS; +} + +static size_t audio_get_input_buffer_size(const struct audio_hw_device *dev, + const struct audio_config *config) +{ + DBG(""); + return -ENOSYS; +} + +static int audio_open_input_stream_real(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in, + audio_input_flags_t flags, + const char *address, + audio_source_t source) +{ + struct audio_stream_in *in; + + DBG(""); + + in = calloc(1, sizeof(struct audio_stream_in)); + if (!in) + return -ENOMEM; + + in->common.get_sample_rate = in_get_sample_rate; + in->common.set_sample_rate = in_set_sample_rate; + in->common.get_buffer_size = in_get_buffer_size; + in->common.get_channels = in_get_channels; + in->common.get_format = in_get_format; + in->common.set_format = in_set_format; + in->common.standby = in_standby; + in->common.dump = in_dump; + in->common.set_parameters = in_set_parameters; + in->common.get_parameters = in_get_parameters; + in->common.add_audio_effect = in_add_audio_effect; + in->common.remove_audio_effect = in_remove_audio_effect; + in->set_gain = in_set_gain; + in->read = in_read; + in->get_input_frames_lost = in_get_input_frames_lost; + + *stream_in = in; + + return 0; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int audio_open_input_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in, + audio_input_flags_t flags, + const char *address, + audio_source_t source) +{ + return audio_open_input_stream_real(dev, handle, devices, config, + stream_in, flags, address, + source); +} +#else +static int audio_open_input_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in) +{ + return audio_open_input_stream_real(dev, handle, devices, config, + stream_in, 0, NULL, 0); +} +#endif + +static void audio_close_input_stream(struct audio_hw_device *dev, + struct audio_stream_in *stream_in) +{ + DBG(""); + free(stream_in); +} + +static int audio_dump(const audio_hw_device_t *device, int fd) +{ + DBG(""); + return -ENOSYS; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int set_master_mute(struct audio_hw_device *dev, bool mute) +{ + DBG(""); + return -ENOSYS; +} + +static int get_master_mute(struct audio_hw_device *dev, bool *mute) +{ + DBG(""); + return -ENOSYS; +} + +static int create_audio_patch(struct audio_hw_device *dev, + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *handle) +{ + DBG(""); + return -ENOSYS; +} + +static int release_audio_patch(struct audio_hw_device *dev, + audio_patch_handle_t handle) +{ + DBG(""); + return -ENOSYS; +} + +static int get_audio_port(struct audio_hw_device *dev, struct audio_port *port) +{ + DBG(""); + return -ENOSYS; +} + +static int set_audio_port_config(struct audio_hw_device *dev, + const struct audio_port_config *config) +{ + DBG(""); + return -ENOSYS; +} +#endif + +static int audio_close(hw_device_t *device) +{ + struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *)device; + unsigned int i; + + DBG(""); + + unregister_endpoints(); + + for (i = 0; i < NUM_CODECS; i++) { + const struct audio_codec *codec = audio_codecs[i].get_codec(); + + if (!audio_codecs[i].loaded) + continue; + + if (codec->unload) + codec->unload(); + + audio_codecs[i].loaded = false; + } + + shutdown(listen_sk, SHUT_RDWR); + shutdown(audio_sk, SHUT_RDWR); + + pthread_join(ipc_th, NULL); + + close(listen_sk); + listen_sk = -1; + + free(a2dp_dev); + return 0; +} + +static void *ipc_handler(void *data) +{ + bool done = false; + struct pollfd pfd; + int sk; + + DBG(""); + + while (!done) { + DBG("Waiting for connection ..."); + + sk = accept(listen_sk, NULL, NULL); + if (sk < 0) { + int err = errno; + + if (err == EINTR) + continue; + + if (err != ECONNABORTED && err != EINVAL) + error("audio: Failed to accept socket: %d (%s)", + err, strerror(err)); + + break; + } + + pthread_mutex_lock(&sk_mutex); + audio_sk = sk; + pthread_mutex_unlock(&sk_mutex); + + DBG("Audio IPC: Connected"); + + if (register_endpoints() != AUDIO_STATUS_SUCCESS) { + error("audio: Failed to register endpoints"); + + unregister_endpoints(); + + pthread_mutex_lock(&sk_mutex); + shutdown(audio_sk, SHUT_RDWR); + close(audio_sk); + audio_sk = -1; + pthread_mutex_unlock(&sk_mutex); + + continue; + } + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = audio_sk; + pfd.events = POLLHUP | POLLERR | POLLNVAL; + + /* Check if socket is still alive. Empty while loop.*/ + while (poll(&pfd, 1, -1) < 0 && errno == EINTR); + + info("Audio HAL: Socket closed"); + + pthread_mutex_lock(&sk_mutex); + close(audio_sk); + audio_sk = -1; + pthread_mutex_unlock(&sk_mutex); + } + + /* audio_sk is closed at this point, just cleanup endpoints states */ + memset(audio_endpoints, 0, sizeof(audio_endpoints)); + + info("Closing Audio IPC thread"); + return NULL; +} + +static int audio_ipc_init(void) +{ + struct sockaddr_un addr; + int err; + int sk; + + DBG(""); + + sk = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (sk < 0) { + err = -errno; + error("audio: Failed to create socket: %d (%s)", -err, + strerror(-err)); + return err; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + memcpy(addr.sun_path, BLUEZ_AUDIO_SK_PATH, + sizeof(BLUEZ_AUDIO_SK_PATH)); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = -errno; + error("audio: Failed to bind socket: %d (%s)", -err, + strerror(-err)); + goto failed; + } + + if (listen(sk, 1) < 0) { + err = -errno; + error("audio: Failed to listen on the socket: %d (%s)", -err, + strerror(-err)); + goto failed; + } + + listen_sk = sk; + + err = pthread_create(&ipc_th, NULL, ipc_handler, NULL); + if (err) { + err = -err; + ipc_th = 0; + error("audio: Failed to start Audio IPC thread: %d (%s)", + -err, strerror(-err)); + goto failed; + } + + return 0; + +failed: + close(sk); + return err; +} + +static int audio_open(const hw_module_t *module, const char *name, + hw_device_t **device) +{ + struct a2dp_audio_dev *a2dp_dev; + size_t i; + int err; + + DBG(""); + + if (strcmp(name, AUDIO_HARDWARE_INTERFACE)) { + error("audio: interface %s not matching [%s]", name, + AUDIO_HARDWARE_INTERFACE); + return -EINVAL; + } + + err = audio_ipc_init(); + if (err < 0) + return err; + + a2dp_dev = calloc(1, sizeof(struct a2dp_audio_dev)); + if (!a2dp_dev) + return -ENOMEM; + + a2dp_dev->dev.common.tag = HARDWARE_DEVICE_TAG; + a2dp_dev->dev.common.version = AUDIO_DEVICE_API_VERSION_CURRENT; + a2dp_dev->dev.common.module = (struct hw_module_t *) module; + a2dp_dev->dev.common.close = audio_close; + + a2dp_dev->dev.init_check = audio_init_check; + a2dp_dev->dev.set_voice_volume = audio_set_voice_volume; + a2dp_dev->dev.set_master_volume = audio_set_master_volume; + a2dp_dev->dev.set_mode = audio_set_mode; + a2dp_dev->dev.set_mic_mute = audio_set_mic_mute; + a2dp_dev->dev.get_mic_mute = audio_get_mic_mute; + a2dp_dev->dev.set_parameters = audio_set_parameters; + a2dp_dev->dev.get_parameters = audio_get_parameters; + a2dp_dev->dev.get_input_buffer_size = audio_get_input_buffer_size; + a2dp_dev->dev.open_output_stream = audio_open_output_stream; + a2dp_dev->dev.close_output_stream = audio_close_output_stream; + a2dp_dev->dev.open_input_stream = audio_open_input_stream; + a2dp_dev->dev.close_input_stream = audio_close_input_stream; + a2dp_dev->dev.dump = audio_dump; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + a2dp_dev->dev.set_master_mute = set_master_mute; + a2dp_dev->dev.get_master_mute = get_master_mute; + a2dp_dev->dev.create_audio_patch = create_audio_patch; + a2dp_dev->dev.release_audio_patch = release_audio_patch; + a2dp_dev->dev.get_audio_port = get_audio_port; + a2dp_dev->dev.set_audio_port_config = set_audio_port_config; +#endif + + for (i = 0; i < NUM_CODECS; i++) { + const struct audio_codec *codec = audio_codecs[i].get_codec(); + + if (codec->load && !codec->load()) + continue; + + audio_codecs[i].loaded = true; + } + + /* + * Note that &a2dp_dev->dev.common is the same pointer as a2dp_dev. + * This results from the structure of following structs:a2dp_audio_dev, + * audio_hw_device. We will rely on this later in the code. + */ + *device = &a2dp_dev->dev.common; + + return 0; +} + +static struct hw_module_methods_t hal_module_methods = { + .open = audio_open, +}; + +struct audio_module HAL_MODULE_INFO_SYM = { + .common = { + .tag = HARDWARE_MODULE_TAG, + .version_major = 1, + .version_minor = 0, + .id = AUDIO_HARDWARE_MODULE_ID, + .name = "A2DP Bluez HW HAL", + .author = "Intel Corporation", + .methods = &hal_module_methods, + }, +};
diff --git a/android/hal-audio.h b/android/hal-audio.h new file mode 100644 index 0000000..2b47412 --- /dev/null +++ b/android/hal-audio.h
@@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <time.h> +#include <hardware/audio.h> + +#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)); + +#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)); + +#else +#error "Unknown byte order" +#endif + +struct media_packet { + uint8_t data[0]; +}; + +struct media_packet_rtp { + struct rtp_header hdr; + uint8_t data[0]; +}; + +struct audio_input_config { + uint32_t rate; + uint32_t channels; + audio_format_t format; +}; + +struct audio_codec { + uint8_t type; + bool use_rtp; + + bool (*load) (void); + void (*unload) (void); + + int (*get_presets) (struct audio_preset *preset, size_t *len); + + bool (*init) (struct audio_preset *preset, uint16_t mtu, + void **codec_data); + bool (*cleanup) (void *codec_data); + bool (*get_config) (void *codec_data, + struct audio_input_config *config); + size_t (*get_buffer_size) (void *codec_data); + size_t (*get_mediapacket_duration) (void *codec_data); + ssize_t (*encode_mediapacket) (void *codec_data, const uint8_t *buffer, + size_t len, struct media_packet *mp, + size_t mp_data_len, size_t *written); + bool (*update_qos) (void *codec_data, uint8_t op); +}; + +#define QOS_POLICY_DEFAULT 0x00 +#define QOS_POLICY_DECREASE 0x01 + +typedef const struct audio_codec * (*audio_codec_get_t) (void); + +const struct audio_codec *codec_sbc(void); +const struct audio_codec *codec_aptx(void);
diff --git a/android/hal-avrcp-ctrl.c b/android/hal-avrcp-ctrl.c new file mode 100644 index 0000000..46b77fd --- /dev/null +++ b/android/hal-avrcp-ctrl.c
@@ -0,0 +1,145 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +#include "hal-utils.h" +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" + +static const btrc_ctrl_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_connection_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_ctrl_conn_state *ev = buf; + + if (cbs->connection_state_cb) + cbs->connection_state_cb(ev->state, + (bt_bdaddr_t *) (ev->bdaddr)); +} + +static void handle_passthrough_rsp(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_ctrl_passthrough_rsp *ev = buf; + + if (cbs->passthrough_rsp_cb) + cbs->passthrough_rsp_cb(ev->id, ev->key_state); +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_AVRCP_CTRL_CONN_STATE */ + { handle_connection_state, false, + sizeof(struct hal_ev_avrcp_ctrl_conn_state) }, + /* HAL_EV_AVRCP_CTRL_PASSTHROUGH_RSP */ + { handle_passthrough_rsp, false, + sizeof(struct hal_ev_avrcp_ctrl_passthrough_rsp) }, +}; + +static bt_status_t init(btrc_ctrl_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_AVRCP_CTRL, ev_handlers, + sizeof(ev_handlers) / sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_AVRCP_CTRL; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_AVRCP_CTRL); + } + + return ret; +} + +static bt_status_t send_pass_through_cmd(bt_bdaddr_t *bd_addr, uint8_t key_code, + uint8_t key_state) +{ + struct hal_cmd_avrcp_ctrl_send_passthrough cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + cmd.key_code = key_code; + cmd.key_state = key_state; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP_CTRL, + HAL_OP_AVRCP_CTRL_SEND_PASSTHROUGH, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_AVRCP_CTRL; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_AVRCP_CTRL); + + cbs = NULL; +} + +static btrc_ctrl_interface_t iface = { + .size = sizeof(iface), + .init = init, + .send_pass_through_cmd = send_pass_through_cmd, + .cleanup = cleanup +}; + +btrc_ctrl_interface_t *bt_get_avrcp_ctrl_interface(void) +{ + return &iface; +}
diff --git a/android/hal-avrcp.c b/android/hal-avrcp.c new file mode 100644 index 0000000..f935eda --- /dev/null +++ b/android/hal-avrcp.c
@@ -0,0 +1,688 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +#include "hal-utils.h" +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" + +static const btrc_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_remote_features(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_remote_features *ev = buf; + + if (cbs->remote_features_cb) + cbs->remote_features_cb((bt_bdaddr_t *) (ev->bdaddr), + ev->features); +} + +static void handle_get_play_status(void *buf, uint16_t len, int fd) +{ + if (cbs->get_play_status_cb) + cbs->get_play_status_cb(); +} + +static void handle_list_player_attrs(void *buf, uint16_t len, int fd) +{ + if (cbs->list_player_app_attr_cb) + cbs->list_player_app_attr_cb(); +} + +static void handle_list_player_values(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_list_player_values *ev = buf; + + if (cbs->list_player_app_values_cb) + cbs->list_player_app_values_cb(ev->attr); +} + +static void handle_get_player_values(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_get_player_values *ev = buf; + btrc_player_attr_t attrs[4]; + int i; + + if (!cbs->get_player_app_value_cb) + return; + + /* Convert uint8_t array to btrc_player_attr_t array */ + for (i = 0; i < ev->number; i++) + attrs[i] = ev->attrs[i]; + + cbs->get_player_app_value_cb(ev->number, attrs); +} + +static void handle_get_player_attrs_text(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_get_player_attrs_text *ev = buf; + btrc_player_attr_t attrs[4]; + int i; + + if (!cbs->get_player_app_attrs_text_cb) + return; + + /* Convert uint8_t array to btrc_player_attr_t array */ + for (i = 0; i < ev->number; i++) + attrs[i] = ev->attrs[i]; + + cbs->get_player_app_attrs_text_cb(ev->number, attrs); +} + +static void handle_get_player_values_text(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_get_player_values_text *ev = buf; + + if (cbs->get_player_app_values_text_cb) + cbs->get_player_app_values_text_cb(ev->attr, ev->number, + ev->values); +} + +static void handle_set_player_value(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_set_player_values *ev = buf; + struct hal_avrcp_player_attr_value *attrs; + btrc_player_settings_t values; + int i; + + if (!cbs->set_player_app_value_cb) + return; + + attrs = (struct hal_avrcp_player_attr_value *) ev->attrs; + + /* Convert to btrc_player_settings_t */ + values.num_attr = ev->number; + for (i = 0; i < ev->number; i++) { + values.attr_ids[i] = attrs[i].attr; + values.attr_values[i] = attrs[i].value; + } + + cbs->set_player_app_value_cb(&values); +} + +static void handle_get_element_attrs(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_get_element_attrs *ev = buf; + btrc_media_attr_t attrs[BTRC_MAX_APP_SETTINGS]; + int i; + + if (!cbs->get_element_attr_cb) + return; + + /* Convert uint8_t array to btrc_media_attr_t array */ + for (i = 0; i < ev->number; i++) + attrs[i] = ev->attrs[i]; + + cbs->get_element_attr_cb(ev->number, attrs); +} + +static void handle_register_notification(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_register_notification *ev = buf; + + if (cbs->register_notification_cb) + cbs->register_notification_cb(ev->event, ev->param); +} + +static void handle_volume_changed(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_volume_changed *ev = buf; + + if (cbs->volume_change_cb) + cbs->volume_change_cb(ev->volume, ev->type); +} + +static void handle_passthrough_cmd(void *buf, uint16_t len, int fd) +{ + struct hal_ev_avrcp_passthrough_cmd *ev = buf; + + if (cbs->passthrough_cmd_cb) + cbs->passthrough_cmd_cb(ev->id, ev->state); +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_AVRCP_REMOTE_FEATURES */ + { handle_remote_features, false, + sizeof(struct hal_ev_avrcp_remote_features) }, + /* HAL_EV_AVRCP_GET_PLAY_STATUS */ + { handle_get_play_status, false, 0 }, + /* HAL_EV_AVRCP_LIST_PLAYER_ATTRS */ + { handle_list_player_attrs, false, 0 }, + /* HAL_EV_AVRCP_LIST_PLAYER_VALUES */ + { handle_list_player_values, false, + sizeof(struct hal_ev_avrcp_list_player_values) }, + /* HAL_EV_AVRCP_GET_PLAYER_VALUES */ + { handle_get_player_values, true, + sizeof(struct hal_ev_avrcp_get_player_values) }, + /* HAL_EV_AVRCP_GET_PLAYER_ATTRS_TEXT */ + { handle_get_player_attrs_text, true, + sizeof(struct hal_ev_avrcp_get_player_attrs_text) }, + /* HAL_EV_AVRCP_GET_PLAYER_VALUES_TEXT */ + { handle_get_player_values_text, true, + sizeof(struct hal_ev_avrcp_get_player_values_text) }, + /* HAL_EV_AVRCP_SET_PLAYER_VALUES */ + { handle_set_player_value, true, + sizeof(struct hal_ev_avrcp_set_player_values) }, + /* HAL_EV_AVRCP_GET_ELEMENT_ATTRS */ + { handle_get_element_attrs, true, + sizeof(struct hal_ev_avrcp_get_element_attrs) }, + /* HAL_EV_AVRCP_REGISTER_NOTIFICATION */ + { handle_register_notification, false, + sizeof(struct hal_ev_avrcp_register_notification) }, + /* HAL_EV_AVRCP_VOLUME_CHANGED */ + { handle_volume_changed, false, + sizeof(struct hal_ev_avrcp_volume_changed) }, + /* HAL_EV_AVRCP_PASSTHROUGH_CMD */ + { handle_passthrough_cmd, false, + sizeof(struct hal_ev_avrcp_passthrough_cmd) }, +}; + +static bt_status_t init(btrc_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_AVRCP, ev_handlers, + sizeof(ev_handlers) / sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_AVRCP; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_AVRCP); + } + + return ret; +} + +static bt_status_t get_play_status_rsp(btrc_play_status_t status, + uint32_t song_len, uint32_t song_pos) +{ + struct hal_cmd_avrcp_get_play_status cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.status = status; + cmd.duration = song_len; + cmd.position = song_pos; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, HAL_OP_AVRCP_GET_PLAY_STATUS, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t list_player_app_attr_rsp(int num_attr, + btrc_player_attr_t *p_attrs) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_list_player_attrs *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (num_attr < 0) + return BT_STATUS_PARM_INVALID; + + len = sizeof(*cmd) + num_attr; + if (len > IPC_MTU) + return BT_STATUS_PARM_INVALID; + + cmd->number = num_attr; + memcpy(cmd->attrs, p_attrs, num_attr); + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_LIST_PLAYER_ATTRS, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t list_player_app_value_rsp(int num_val, uint8_t *p_vals) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_list_player_values *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (num_val < 0) + return BT_STATUS_PARM_INVALID; + + len = sizeof(*cmd) + num_val; + + if (len > IPC_MTU) + return BT_STATUS_PARM_INVALID; + + cmd->number = num_val; + memcpy(cmd->values, p_vals, num_val); + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_LIST_PLAYER_VALUES, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t get_player_app_value_rsp(btrc_player_settings_t *p_vals) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_get_player_attrs *cmd = (void *) buf; + size_t len, attrs_len; + int i; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!p_vals) + return BT_STATUS_PARM_INVALID; + + attrs_len = p_vals->num_attr * + sizeof(struct hal_avrcp_player_attr_value); + len = sizeof(*cmd) + attrs_len; + + if (len > IPC_MTU) + return BT_STATUS_PARM_INVALID; + + cmd->number = p_vals->num_attr; + + for (i = 0; i < p_vals->num_attr; i++) { + cmd->attrs[i].attr = p_vals->attr_ids[i]; + cmd->attrs[i].value = p_vals->attr_values[i]; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAYER_ATTRS, + len, cmd, NULL, NULL, NULL); +} + +static int write_text(uint8_t *ptr, uint8_t id, uint8_t *text, size_t *len) +{ + struct hal_avrcp_player_setting_text *value = (void *) ptr; + size_t attr_len = sizeof(*value); + + if (attr_len + *len > IPC_MTU) + return 0; + + value->id = id; + value->len = strnlen((const char *) text, BTRC_MAX_ATTR_STR_LEN); + + *len += attr_len; + + if (value->len + *len > IPC_MTU) + value->len = IPC_MTU - *len; + + memcpy(value->text, text, value->len); + + *len += value->len; + + return attr_len + value->len; +} + +static uint8_t write_player_setting_text(uint8_t *ptr, uint8_t num_attr, + btrc_player_setting_text_t *p_attrs, + size_t *len) +{ + int i; + + for (i = 0; i < num_attr && *len < IPC_MTU; i++) { + int ret; + + ret = write_text(ptr, p_attrs[i].id, p_attrs[i].text, len); + if (ret == 0) + break; + + ptr += ret; + } + + return i; +} + +static bt_status_t get_player_app_attr_text_rsp(int num_attr, + btrc_player_setting_text_t *p_attrs) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_get_player_attrs_text *cmd = (void *) buf; + uint8_t *ptr; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (num_attr < 0 || num_attr > BTRC_MAX_APP_SETTINGS) + return BT_STATUS_PARM_INVALID; + + len = sizeof(*cmd); + ptr = (uint8_t *) &cmd->attrs[0]; + cmd->number = write_player_setting_text(ptr, num_attr, p_attrs, &len); + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAYER_ATTRS_TEXT, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t get_player_app_value_text_rsp(int num_val, + btrc_player_setting_text_t *p_vals) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_get_player_values_text *cmd = (void *) buf; + uint8_t *ptr; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (num_val < 0) + return BT_STATUS_PARM_INVALID; + + len = sizeof(*cmd); + ptr = (uint8_t *) &cmd->values[0]; + cmd->number = write_player_setting_text(ptr, num_val, p_vals, &len); + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_PLAYER_VALUES_TEXT, + len, cmd, NULL, NULL, NULL); +} + +static uint8_t write_element_attr_text(uint8_t *ptr, uint8_t num_attr, + btrc_element_attr_val_t *p_attrs, + size_t *len) +{ + int i; + + for (i = 0; i < num_attr && *len < IPC_MTU; i++) { + int ret; + + ret = write_text(ptr, p_attrs[i].attr_id, p_attrs[i].text, len); + if (ret == 0) + break; + + ptr += ret; + } + + return i; +} + +static bt_status_t get_element_attr_rsp(uint8_t num_attr, + btrc_element_attr_val_t *p_attrs) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_get_element_attrs_text *cmd = (void *) buf; + size_t len; + uint8_t *ptr; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + len = sizeof(*cmd); + ptr = (uint8_t *) &cmd->values[0]; + cmd->number = write_element_attr_text(ptr, num_attr, p_attrs, &len); + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_GET_ELEMENT_ATTRS_TEXT, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t set_player_app_value_rsp(btrc_status_t rsp_status) +{ + struct hal_cmd_avrcp_set_player_attrs_value cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.status = rsp_status; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_SET_PLAYER_ATTRS_VALUE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t play_status_changed_rsp(btrc_notification_type_t type, + btrc_play_status_t *play_status) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_register_notification *cmd = (void *) buf; + size_t len; + + cmd->event = BTRC_EVT_PLAY_STATUS_CHANGED; + cmd->type = type; + cmd->len = 1; + memcpy(cmd->data, play_status, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t track_change_rsp(btrc_notification_type_t type, + btrc_uid_t *track) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_register_notification *cmd = (void *) buf; + size_t len; + + cmd->event = BTRC_EVT_TRACK_CHANGE; + cmd->type = type; + cmd->len = sizeof(*track); + memcpy(cmd->data, track, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t track_reached_end_rsp(btrc_notification_type_t type) +{ + struct hal_cmd_avrcp_register_notification cmd; + + cmd.event = BTRC_EVT_TRACK_REACHED_END; + cmd.type = type; + cmd.len = 0; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t track_reached_start_rsp(btrc_notification_type_t type) +{ + struct hal_cmd_avrcp_register_notification cmd; + + cmd.event = BTRC_EVT_TRACK_REACHED_START; + cmd.type = type; + cmd.len = 0; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t play_pos_changed_rsp(btrc_notification_type_t type, + uint32_t *song_pos) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_register_notification *cmd = (void *) buf; + size_t len; + + cmd->event = BTRC_EVT_PLAY_POS_CHANGED; + cmd->type = type; + cmd->len = sizeof(*song_pos); + memcpy(cmd->data, song_pos, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t settings_changed_rsp(btrc_notification_type_t type, + btrc_player_settings_t *player_setting) +{ + char buf[IPC_MTU]; + struct hal_cmd_avrcp_register_notification *cmd = (void *) buf; + struct hal_avrcp_player_attr_value *attrs; + size_t len, param_len; + int i; + + param_len = player_setting->num_attr * sizeof(*attrs); + len = sizeof(*cmd) + param_len; + + if (len > IPC_MTU) + return BT_STATUS_PARM_INVALID; + + cmd->event = BTRC_EVT_APP_SETTINGS_CHANGED; + cmd->type = type; + cmd->len = param_len; + + attrs = (struct hal_avrcp_player_attr_value *) &cmd->data[0]; + for (i = 0; i < player_setting->num_attr; i++) { + attrs[i].attr = player_setting->attr_ids[i]; + attrs[i].value = player_setting->attr_values[i]; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, + HAL_OP_AVRCP_REGISTER_NOTIFICATION, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t register_notification_rsp(btrc_event_id_t event_id, + btrc_notification_type_t type, + btrc_register_notification_t *p_param) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + switch (event_id) { + case BTRC_EVT_PLAY_STATUS_CHANGED: + return play_status_changed_rsp(type, &p_param->play_status); + case BTRC_EVT_TRACK_CHANGE: + return track_change_rsp(type, &p_param->track); + case BTRC_EVT_TRACK_REACHED_END: + return track_reached_end_rsp(type); + case BTRC_EVT_TRACK_REACHED_START: + return track_reached_start_rsp(type); + case BTRC_EVT_PLAY_POS_CHANGED: + return play_pos_changed_rsp(type, &p_param->song_pos); + case BTRC_EVT_APP_SETTINGS_CHANGED: + return settings_changed_rsp(type, &p_param->player_setting); + default: + return BT_STATUS_PARM_INVALID; + } +} + +static bt_status_t set_volume(uint8_t volume) +{ + struct hal_cmd_avrcp_set_volume cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.value = volume; + + return hal_ipc_cmd(HAL_SERVICE_ID_AVRCP, HAL_OP_AVRCP_SET_VOLUME, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_AVRCP; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_AVRCP); + + cbs = NULL; +} + +static btrc_interface_t iface = { + .size = sizeof(iface), + .init = init, + .get_play_status_rsp = get_play_status_rsp, + .list_player_app_attr_rsp = list_player_app_attr_rsp, + .list_player_app_value_rsp = list_player_app_value_rsp, + .get_player_app_value_rsp = get_player_app_value_rsp, + .get_player_app_attr_text_rsp = get_player_app_attr_text_rsp, + .get_player_app_value_text_rsp = get_player_app_value_text_rsp, + .get_element_attr_rsp = get_element_attr_rsp, + .set_player_app_value_rsp = set_player_app_value_rsp, + .register_notification_rsp = register_notification_rsp, + .set_volume = set_volume, + .cleanup = cleanup +}; + +btrc_interface_t *bt_get_avrcp_interface(void) +{ + return &iface; +}
diff --git a/android/hal-bluetooth.c b/android/hal-bluetooth.c new file mode 100644 index 0000000..66f4a37 --- /dev/null +++ b/android/hal-bluetooth.c
@@ -0,0 +1,1136 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <cutils/properties.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" +#include "hal-utils.h" + +static const bt_callbacks_t *bt_hal_cbacks = NULL; + +#define enum_prop_to_hal(prop, hal_prop, type) do { \ + static type e; \ + prop.val = &e; \ + prop.len = sizeof(e); \ + e = *((uint8_t *) (hal_prop->val)); \ +} while (0) + +#define enum_prop_from_hal(prop, hal_len, hal_val, enum_type) do { \ + enum_type e; \ + if (prop->len != sizeof(e)) { \ + error("invalid HAL property %u (%u vs %zu), aborting ", \ + prop->type, prop->len, sizeof(e)); \ + exit(EXIT_FAILURE); \ + } \ + memcpy(&e, prop->val, sizeof(e)); \ + *((uint8_t *) hal_val) = e; /* enums are mapped to 1 byte */ \ + *hal_len = 1; \ +} while (0) + +static void handle_adapter_state_changed(void *buf, uint16_t len, int fd) +{ + struct hal_ev_adapter_state_changed *ev = buf; + + DBG("state: %s", bt_state_t2str(ev->state)); + + if (bt_hal_cbacks->adapter_state_changed_cb) + bt_hal_cbacks->adapter_state_changed_cb(ev->state); +} + +static void adapter_props_to_hal(bt_property_t *send_props, + struct hal_property *prop, + uint8_t num_props, uint16_t len) +{ + void *buf = prop; + uint8_t i; + + for (i = 0; i < num_props; i++) { + if (sizeof(*prop) + prop->len > len) { + error("invalid adapter properties(%zu > %u), aborting", + sizeof(*prop) + prop->len, len); + exit(EXIT_FAILURE); + } + + send_props[i].type = prop->type; + + switch (prop->type) { + case HAL_PROP_ADAPTER_TYPE: + enum_prop_to_hal(send_props[i], prop, + bt_device_type_t); + break; + case HAL_PROP_ADAPTER_SCAN_MODE: + enum_prop_to_hal(send_props[i], prop, + bt_scan_mode_t); + break; + case HAL_PROP_ADAPTER_SERVICE_REC: + default: + send_props[i].len = prop->len; + send_props[i].val = prop->val; + break; + } + + DBG("prop[%d]: %s", i, btproperty2str(&send_props[i])); + + len -= sizeof(*prop) + prop->len; + buf += sizeof(*prop) + prop->len; + prop = buf; + } + + if (!len) + return; + + error("invalid adapter properties (%u bytes left), aborting", len); + exit(EXIT_FAILURE); +} + +static void adapter_prop_from_hal(const bt_property_t *property, uint8_t *type, + uint16_t *len, void *val) +{ + /* type match IPC type */ + *type = property->type; + + switch (property->type) { + case HAL_PROP_ADAPTER_SCAN_MODE: + enum_prop_from_hal(property, len, val, bt_scan_mode_t); + break; + case BT_PROPERTY_BDNAME: + case BT_PROPERTY_BDADDR: + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_CLASS_OF_DEVICE: + case BT_PROPERTY_TYPE_OF_DEVICE: + case BT_PROPERTY_SERVICE_RECORD: + case BT_PROPERTY_ADAPTER_BONDED_DEVICES: + case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT: + case BT_PROPERTY_REMOTE_FRIENDLY_NAME: + case BT_PROPERTY_REMOTE_RSSI: + case BT_PROPERTY_REMOTE_VERSION_INFO: + case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP: +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + case BT_PROPERTY_LOCAL_LE_FEATURES: +#endif + default: + *len = property->len; + memcpy(val, property->val, property->len); + break; + } +} + +static void device_props_to_hal(bt_property_t *send_props, + struct hal_property *prop, uint8_t num_props, + uint16_t len) +{ + void *buf = prop; + uint8_t i; + + for (i = 0; i < num_props; i++) { + if (sizeof(*prop) + prop->len > len) { + error("invalid device properties (%zu > %u), aborting", + sizeof(*prop) + prop->len, len); + exit(EXIT_FAILURE); + } + + send_props[i].type = prop->type; + + switch (prop->type) { + case HAL_PROP_DEVICE_TYPE: + enum_prop_to_hal(send_props[i], prop, + bt_device_type_t); + break; + case HAL_PROP_DEVICE_VERSION_INFO: + { + static bt_remote_version_t e; + const struct hal_prop_device_info *p; + + send_props[i].val = &e; + send_props[i].len = sizeof(e); + + p = (struct hal_prop_device_info *) prop->val; + + e.manufacturer = p->manufacturer; + e.sub_ver = p->sub_version; + e.version = p->version; + } + break; + case HAL_PROP_DEVICE_SERVICE_REC: + { + static bt_service_record_t e; + const struct hal_prop_device_service_rec *p; + + send_props[i].val = &e; + send_props[i].len = sizeof(e); + + p = (struct hal_prop_device_service_rec *) prop->val; + + memset(&e, 0, sizeof(e)); + memcpy(&e.channel, &p->channel, sizeof(e.channel)); + memcpy(e.uuid.uu, p->uuid, sizeof(e.uuid.uu)); + memcpy(e.name, p->name, p->name_len); + } + break; + default: + send_props[i].len = prop->len; + send_props[i].val = prop->val; + break; + } + + len -= sizeof(*prop) + prop->len; + buf += sizeof(*prop) + prop->len; + prop = buf; + + DBG("prop[%d]: %s", i, btproperty2str(&send_props[i])); + } + + if (!len) + return; + + error("invalid device properties (%u bytes left), aborting", len); + exit(EXIT_FAILURE); +} + +static void handle_adapter_props_changed(void *buf, uint16_t len, int fd) +{ + struct hal_ev_adapter_props_changed *ev = buf; + bt_property_t props[ev->num_props]; + + DBG(""); + + if (!bt_hal_cbacks->adapter_properties_cb) + return; + + len -= sizeof(*ev); + adapter_props_to_hal(props, ev->props, ev->num_props, len); + + bt_hal_cbacks->adapter_properties_cb(ev->status, ev->num_props, props); +} + +static void handle_bond_state_change(void *buf, uint16_t len, int fd) +{ + struct hal_ev_bond_state_changed *ev = buf; + bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr; + + DBG("state %u", ev->state); + + if (bt_hal_cbacks->bond_state_changed_cb) + bt_hal_cbacks->bond_state_changed_cb(ev->status, addr, + ev->state); +} + +static void handle_pin_request(void *buf, uint16_t len, int fd) +{ + struct hal_ev_pin_request *ev = buf; + /* Those are declared as packed, so it's safe to assign pointers */ + bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr; + bt_bdname_t *name = (bt_bdname_t *) ev->name; + + DBG(""); + + if (bt_hal_cbacks->pin_request_cb) + bt_hal_cbacks->pin_request_cb(addr, name, ev->class_of_dev); +} + +static void handle_ssp_request(void *buf, uint16_t len, int fd) +{ + struct hal_ev_ssp_request *ev = buf; + /* Those are declared as packed, so it's safe to assign pointers */ + bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr; + bt_bdname_t *name = (bt_bdname_t *) ev->name; + + DBG(""); + + if (bt_hal_cbacks->ssp_request_cb) + bt_hal_cbacks->ssp_request_cb(addr, name, ev->class_of_dev, + ev->pairing_variant, + ev->passkey); +} + +void bt_thread_associate(void) +{ + if (bt_hal_cbacks->thread_evt_cb) + bt_hal_cbacks->thread_evt_cb(ASSOCIATE_JVM); +} + +void bt_thread_disassociate(void) +{ + if (bt_hal_cbacks->thread_evt_cb) + bt_hal_cbacks->thread_evt_cb(DISASSOCIATE_JVM); +} + +static bool interface_ready(void) +{ + return bt_hal_cbacks != NULL; +} + +static void handle_discovery_state_changed(void *buf, uint16_t len, int fd) +{ + struct hal_ev_discovery_state_changed *ev = buf; + + DBG(""); + + if (bt_hal_cbacks->discovery_state_changed_cb) + bt_hal_cbacks->discovery_state_changed_cb(ev->state); +} + +static void handle_device_found(void *buf, uint16_t len, int fd) +{ + struct hal_ev_device_found *ev = buf; + bt_property_t props[ev->num_props]; + + DBG(""); + + if (!bt_hal_cbacks->device_found_cb) + return; + + len -= sizeof(*ev); + device_props_to_hal(props, ev->props, ev->num_props, len); + + bt_hal_cbacks->device_found_cb(ev->num_props, props); +} + +static void handle_device_state_changed(void *buf, uint16_t len, int fd) +{ + struct hal_ev_remote_device_props *ev = buf; + bt_property_t props[ev->num_props]; + + DBG(""); + + if (!bt_hal_cbacks->remote_device_properties_cb) + return; + + len -= sizeof(*ev); + device_props_to_hal(props, ev->props, ev->num_props, len); + + bt_hal_cbacks->remote_device_properties_cb(ev->status, + (bt_bdaddr_t *)ev->bdaddr, + ev->num_props, props); +} + +static void handle_acl_state_changed(void *buf, uint16_t len, int fd) +{ + struct hal_ev_acl_state_changed *ev = buf; + bt_bdaddr_t *addr = (bt_bdaddr_t *) ev->bdaddr; + + DBG("state %u", ev->state); + + if (bt_hal_cbacks->acl_state_changed_cb) + bt_hal_cbacks->acl_state_changed_cb(ev->status, addr, + ev->state); +} + +static void handle_dut_mode_receive(void *buf, uint16_t len, int fd) +{ + struct hal_ev_dut_mode_receive *ev = buf; + + DBG(""); + + if (len != sizeof(*ev) + ev->len) { + error("invalid dut mode receive event (%u), aborting", len); + exit(EXIT_FAILURE); + } + + if (bt_hal_cbacks->dut_mode_recv_cb) + bt_hal_cbacks->dut_mode_recv_cb(ev->opcode, ev->data, ev->len); +} + +static void handle_le_test_mode(void *buf, uint16_t len, int fd) +{ + struct hal_ev_le_test_mode *ev = buf; + + DBG(""); + + if (bt_hal_cbacks->le_test_mode_cb) + bt_hal_cbacks->le_test_mode_cb(ev->status, ev->num_packets); +} + +static void handle_energy_info(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_energy_info *ev = buf; + bt_activity_energy_info info; + + DBG(""); + + info.ctrl_state = ev->ctrl_state; + info.energy_used = ev->energy_used; + info.idle_time = ev->idle_time; + info.rx_time = ev->rx_time; + info.status = ev->status; + info.tx_time = ev->status; + + if (bt_hal_cbacks->energy_info_cb) + bt_hal_cbacks->energy_info_cb(&info); +#endif +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_ADAPTER_STATE_CHANGED */ + { handle_adapter_state_changed, false, + sizeof(struct hal_ev_adapter_state_changed) }, + /* HAL_EV_ADAPTER_PROPS_CHANGED */ + { handle_adapter_props_changed, true, + sizeof(struct hal_ev_adapter_props_changed) + + sizeof(struct hal_property) }, + /* HAL_EV_REMOTE_DEVICE_PROPS */ + { handle_device_state_changed, true, + sizeof(struct hal_ev_remote_device_props) + + sizeof(struct hal_property) }, + /* HAL_EV_DEVICE_FOUND */ + { handle_device_found, true, sizeof(struct hal_ev_device_found) + + sizeof(struct hal_property) }, + /* HAL_EV_DISCOVERY_STATE_CHANGED */ + { handle_discovery_state_changed, false, + sizeof(struct hal_ev_discovery_state_changed) }, + /* HAL_EV_PIN_REQUEST */ + { handle_pin_request, false, sizeof(struct hal_ev_pin_request) }, + /* HAL_EV_SSP_REQUEST */ + { handle_ssp_request, false, sizeof(struct hal_ev_ssp_request) }, + /* HAL_EV_BOND_STATE_CHANGED */ + { handle_bond_state_change, false, + sizeof(struct hal_ev_bond_state_changed) }, + /* HAL_EV_ACL_STATE_CHANGED */ + { handle_acl_state_changed, false, + sizeof(struct hal_ev_acl_state_changed) }, + /* HAL_EV_DUT_MODE_RECEIVE */ + { handle_dut_mode_receive, true, + sizeof(struct hal_ev_dut_mode_receive) }, + /* HAL_EV_LE_TEST_MODE */ + { handle_le_test_mode, false, sizeof(struct hal_ev_le_test_mode) }, + /* HAL_EV_ENERGY_INFO */ + { handle_energy_info, false, sizeof(struct hal_ev_energy_info) }, +}; + +static uint8_t get_mode(void) +{ + char value[PROPERTY_VALUE_MAX]; + + if (get_config("mode", value, NULL) > 0) { + if (!strcasecmp(value, "bredr")) + return HAL_MODE_BREDR; + + if (!strcasecmp(value, "le")) + return HAL_MODE_LE; + } + + return HAL_MODE_DEFAULT; +} + +static uint16_t add_prop(const char *prop, uint8_t type, void *buf) +{ + struct hal_config_prop *hal_prop = buf; + + hal_prop->type = type; + hal_prop->len = strlen(prop) + 1; + memcpy(hal_prop->val, prop, hal_prop->len); + + return sizeof(*hal_prop) + hal_prop->len; +} + +static int send_configuration(void) +{ + char buf[IPC_MTU]; + struct hal_cmd_configuration *cmd = (void *) buf; + char prop[PROPERTY_VALUE_MAX]; + uint16_t len = sizeof(*cmd); + + cmd->num = 0; + + if (get_config("vendor", prop, "ro.product.manufacturer") > 0) { + len += add_prop(prop, HAL_CONFIG_VENDOR, buf + len); + cmd->num++; + } + + if (get_config("name", prop, "ro.product.name") > 0) { + len += add_prop(prop, HAL_CONFIG_NAME, buf + len); + cmd->num++; + } + + if (get_config("model", prop, "ro.product.model") > 0) { + len += add_prop(prop, HAL_CONFIG_MODEL, buf + len); + cmd->num++; + } + + if (get_config("serialno", prop, "ro.serialno") > 0) { + len += add_prop(prop, HAL_CONFIG_SERIAL_NUMBER, buf + len); + cmd->num++; + } + + if (get_config("systemid", prop, NULL) > 0) { + len += add_prop(prop, HAL_CONFIG_SYSTEM_ID, buf + len); + cmd->num++; + } + + if (get_config("pnpid", prop, NULL) > 0) { + len += add_prop(prop, HAL_CONFIG_PNP_ID, buf + len); + cmd->num++; + } + + if (get_config("fwrev", prop, "ro.build.version.release") > 0) { + len += add_prop(prop, HAL_CONFIG_FW_REV, buf + len); + cmd->num++; + } + + if (get_config("hwrev", prop, "ro.board.platform") > 0) { + len += add_prop(prop, HAL_CONFIG_HW_REV, buf + len); + cmd->num++; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_CONFIGURATION, len, cmd, + NULL, NULL, NULL); +} + +static int init(bt_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int status; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + hal_ipc_register(HAL_SERVICE_ID_BLUETOOTH, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + if (!hal_ipc_init(BLUEZ_HAL_SK_PATH, sizeof(BLUEZ_HAL_SK_PATH))) + return BT_STATUS_FAIL; + + bt_hal_cbacks = callbacks; + + /* Start Android Bluetooth daemon service */ + if (property_set("bluetooth.start", "daemon") < 0) { + error("Failed to set bluetooth.start=daemon"); + hal_ipc_cleanup(); + bt_hal_cbacks = NULL; + return BT_STATUS_FAIL; + } + + if (!hal_ipc_accept()) { + hal_ipc_cleanup(); + bt_hal_cbacks = NULL; + return BT_STATUS_FAIL; + } + + status = send_configuration(); + if (status != BT_STATUS_SUCCESS) { + error("Failed to send configuration"); + goto fail; + } + + cmd.service_id = HAL_SERVICE_ID_BLUETOOTH; + cmd.mode = get_mode(); + cmd.max_clients = 1; + + status = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + if (status != BT_STATUS_SUCCESS) { + error("Failed to register 'bluetooth' service"); + goto fail; + } + + cmd.service_id = HAL_SERVICE_ID_SOCKET; + cmd.max_clients = 1; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cmd.mode = HAL_MODE_SOCKET_DYNAMIC_MAP; +#else + cmd.mode = HAL_MODE_DEFAULT; +#endif + + status = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + if (status != BT_STATUS_SUCCESS) { + error("Failed to register 'socket' service"); + goto fail; + } + + return status; + +fail: + hal_ipc_cleanup(); + bt_hal_cbacks = NULL; + + hal_ipc_unregister(HAL_SERVICE_ID_BLUETOOTH); + + return status; +} + +static int enable(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_ENABLE, 0, NULL, + NULL, NULL, NULL); +} + +static int disable(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DISABLE, 0, NULL, + NULL, NULL, NULL); +} + +static void cleanup(void) +{ + DBG(""); + + if (!interface_ready()) + return; + + hal_ipc_cleanup(); + + hal_ipc_unregister(HAL_SERVICE_ID_BLUETOOTH); + + bt_hal_cbacks = NULL; +} + +static int get_adapter_properties(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_GET_ADAPTER_PROPS, + 0, NULL, NULL, NULL, NULL); +} + +static int get_adapter_property(bt_property_type_t type) +{ + struct hal_cmd_get_adapter_prop cmd; + + DBG("prop: %s (%d)", bt_property_type_t2str(type), type); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + /* type match IPC type */ + cmd.type = type; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_GET_ADAPTER_PROP, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int set_adapter_property(const bt_property_t *property) +{ + char buf[IPC_MTU]; + struct hal_cmd_set_adapter_prop *cmd = (void *) buf; + size_t len; + + DBG("prop: %s", btproperty2str(property)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + adapter_prop_from_hal(property, &cmd->type, &cmd->len, cmd->val); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_SET_ADAPTER_PROP, + len, cmd, NULL, NULL, NULL); +} + +static int get_remote_device_properties(bt_bdaddr_t *remote_addr) +{ + struct hal_cmd_get_remote_device_props cmd; + + DBG("bdaddr: %s", bdaddr2str(remote_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROPS, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int get_remote_device_property(bt_bdaddr_t *remote_addr, + bt_property_type_t type) +{ + struct hal_cmd_get_remote_device_prop cmd; + + DBG("bdaddr: %s prop: %s", bdaddr2str(remote_addr), + bt_property_type_t2str(type)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr)); + + /* type match IPC type */ + cmd.type = type; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROP, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int set_remote_device_property(bt_bdaddr_t *remote_addr, + const bt_property_t *property) +{ + char buf[IPC_MTU]; + struct hal_cmd_set_remote_device_prop *cmd = (void *) buf; + size_t len; + + DBG("bdaddr: %s prop: %s", bdaddr2str(remote_addr), + bt_property_type_t2str(property->type)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd->bdaddr, remote_addr, sizeof(cmd->bdaddr)); + + /* type match IPC type */ + cmd->type = property->type; + cmd->len = property->len; + memcpy(cmd->val, property->val, property->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SET_REMOTE_DEVICE_PROP, + len, cmd, NULL, NULL, NULL); +} + +static int get_remote_service_record(bt_bdaddr_t *remote_addr, bt_uuid_t *uuid) +{ + struct hal_cmd_get_remote_service_rec cmd; + + DBG("bdaddr: %s", bdaddr2str(remote_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr)); + memcpy(cmd.uuid, uuid, sizeof(cmd.uuid)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICE_REC, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int get_remote_services(bt_bdaddr_t *remote_addr) +{ + struct hal_cmd_get_remote_services cmd; + + DBG("bdaddr: %s", bdaddr2str(remote_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, remote_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_GET_REMOTE_SERVICES, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int start_discovery(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_START_DISCOVERY, 0, + NULL, NULL, NULL, NULL); +} + +static int cancel_discovery(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CANCEL_DISCOVERY, 0, + NULL, NULL, NULL, NULL); +} + +static int create_bond_real(const bt_bdaddr_t *bd_addr, int transport) +{ + struct hal_cmd_create_bond cmd; + + DBG("bdaddr: %s", bdaddr2str(bd_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.transport = transport; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CREATE_BOND, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int create_bond(const bt_bdaddr_t *bd_addr, int transport) +{ + return create_bond_real(bd_addr, transport); +} +#else +static int create_bond(const bt_bdaddr_t *bd_addr) +{ + return create_bond_real(bd_addr, BT_TRANSPORT_UNKNOWN); +} +#endif + +static int cancel_bond(const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_cancel_bond cmd; + + DBG("bdaddr: %s", bdaddr2str(bd_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_CANCEL_BOND, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int remove_bond(const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_remove_bond cmd; + + DBG("bdaddr: %s", bdaddr2str(bd_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_REMOVE_BOND, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int pin_reply(const bt_bdaddr_t *bd_addr, uint8_t accept, + uint8_t pin_len, bt_pin_code_t *pin_code) +{ + struct hal_cmd_pin_reply cmd; + + DBG("bdaddr: %s", bdaddr2str(bd_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + cmd.accept = accept; + cmd.pin_len = pin_len; + memcpy(cmd.pin_code, pin_code, sizeof(cmd.pin_code)); + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_PIN_REPLY, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int ssp_reply(const bt_bdaddr_t *bd_addr, bt_ssp_variant_t variant, + uint8_t accept, uint32_t passkey) +{ + struct hal_cmd_ssp_reply cmd; + + DBG("bdaddr: %s", bdaddr2str(bd_addr)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + /* type match IPC type */ + cmd.ssp_variant = variant; + cmd.accept = accept; + cmd.passkey = passkey; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_SSP_REPLY, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static const void *get_profile_interface(const char *profile_id) +{ + DBG("%s", profile_id); + + if (!interface_ready()) + return NULL; + + if (!strcmp(profile_id, BT_PROFILE_SOCKETS_ID)) + return bt_get_socket_interface(); + + if (!strcmp(profile_id, BT_PROFILE_HIDHOST_ID)) + return bt_get_hidhost_interface(); + + if (!strcmp(profile_id, BT_PROFILE_PAN_ID)) + return bt_get_pan_interface(); + + if (!strcmp(profile_id, BT_PROFILE_ADVANCED_AUDIO_ID)) + return bt_get_a2dp_interface(); + + if (!strcmp(profile_id, BT_PROFILE_AV_RC_ID)) + return bt_get_avrcp_interface(); + + if (!strcmp(profile_id, BT_PROFILE_HANDSFREE_ID)) + return bt_get_handsfree_interface(); + + if (!strcmp(profile_id, BT_PROFILE_GATT_ID)) + return bt_get_gatt_interface(); + + if (!strcmp(profile_id, BT_PROFILE_HEALTH_ID)) + return bt_get_health_interface(); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + if (!strcmp(profile_id, BT_PROFILE_AV_RC_CTRL_ID)) + return bt_get_avrcp_ctrl_interface(); + + if (!strcmp(profile_id, BT_PROFILE_HANDSFREE_CLIENT_ID)) + return bt_get_hf_client_interface(); + + if (!strcmp(profile_id, BT_PROFILE_MAP_CLIENT_ID)) + return bt_get_map_client_interface(); + + if (!strcmp(profile_id, BT_PROFILE_ADVANCED_AUDIO_SINK_ID)) + return bt_get_a2dp_sink_interface(); +#endif + + return NULL; +} + +static int dut_mode_configure(uint8_t enable) +{ + struct hal_cmd_dut_mode_conf cmd; + + DBG("enable %u", enable); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.enable = enable; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DUT_MODE_CONF, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int dut_mode_send(uint16_t opcode, uint8_t *buf, uint8_t buf_len) +{ + char cmd_buf[IPC_MTU]; + struct hal_cmd_dut_mode_send *cmd = (void *) cmd_buf; + size_t len; + + DBG("opcode %u len %u", opcode, buf_len); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->opcode = opcode; + cmd->len = buf_len; + memcpy(cmd->data, buf, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_DUT_MODE_SEND, + len, cmd, NULL, NULL, NULL); +} + +static int le_test_mode(uint16_t opcode, uint8_t *buf, uint8_t buf_len) +{ + char cmd_buf[IPC_MTU]; + struct hal_cmd_le_test_mode *cmd = (void *) cmd_buf; + size_t len; + + DBG("opcode %u len %u", opcode, buf_len); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->opcode = opcode; + cmd->len = buf_len; + memcpy(cmd->data, buf, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_LE_TEST_MODE, + len, cmd, NULL, NULL, NULL); +} + +static int config_hci_snoop_log(uint8_t enable) +{ + const char *property; + + DBG("enable %u", enable); + + property = enable ? "bluetooth.start" : "bluetooth.stop"; + + if (property_set(property, "snoop") < 0) { + error("Failed to set %s=snoop", property); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int get_connection_state(const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_get_connection_state cmd; + struct hal_rsp_get_connection_state rsp; + size_t rsp_len = sizeof(rsp); + bt_status_t status; + + DBG("bdaddr: %s", bdaddr2str(bd_addr)); + + if (!interface_ready()) + return 0; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + status = hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_CONNECTION_STATE, sizeof(cmd), &cmd, + &rsp_len, &rsp, NULL); + + if (status != BT_STATUS_SUCCESS) + return 0; + + return rsp.connection_state; +} + +static int set_os_callouts(bt_os_callouts_t *callouts) +{ + DBG("callouts: %p", callouts); + + /* TODO: implement */ + + return BT_STATUS_SUCCESS; +} + +static int read_energy_info(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_BLUETOOTH, HAL_OP_READ_ENERGY_INFO, 0, + NULL, NULL, NULL, NULL); +} +#endif + +static const bt_interface_t bluetooth_if = { + .size = sizeof(bt_interface_t), + .init = init, + .enable = enable, + .disable = disable, + .cleanup = cleanup, + .get_adapter_properties = get_adapter_properties, + .get_adapter_property = get_adapter_property, + .set_adapter_property = set_adapter_property, + .get_remote_device_properties = get_remote_device_properties, + .get_remote_device_property = get_remote_device_property, + .set_remote_device_property = set_remote_device_property, + .get_remote_service_record = get_remote_service_record, + .get_remote_services = get_remote_services, + .start_discovery = start_discovery, + .cancel_discovery = cancel_discovery, + .create_bond = create_bond, + .remove_bond = remove_bond, + .cancel_bond = cancel_bond, + .pin_reply = pin_reply, + .ssp_reply = ssp_reply, + .get_profile_interface = get_profile_interface, + .dut_mode_configure = dut_mode_configure, + .dut_mode_send = dut_mode_send, + .le_test_mode = le_test_mode, + .config_hci_snoop_log = config_hci_snoop_log, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .get_connection_state = get_connection_state, + .set_os_callouts = set_os_callouts, + .read_energy_info = read_energy_info, +#endif +}; + +static const bt_interface_t *get_bluetooth_interface(void) +{ + DBG(""); + + return &bluetooth_if; +} + +static int close_bluetooth(struct hw_device_t *device) +{ + DBG(""); + + cleanup(); + + free(device); + + return 0; +} + +static int open_bluetooth(const struct hw_module_t *module, char const *name, + struct hw_device_t **device) +{ + bluetooth_device_t *dev = malloc(sizeof(bluetooth_device_t)); + + DBG(""); + + if (!dev) { + error("Failed to allocate memory for device"); + return -ENOMEM; + } + + memset(dev, 0, sizeof(bluetooth_device_t)); + dev->common.tag = HARDWARE_DEVICE_TAG; + dev->common.version = 0; + dev->common.module = (struct hw_module_t *) module; + dev->common.close = close_bluetooth; + dev->get_bluetooth_interface = get_bluetooth_interface; + + *device = (struct hw_device_t *) dev; + + return 0; +} + +static struct hw_module_methods_t bluetooth_module_methods = { + .open = open_bluetooth, +}; + +struct hw_module_t HAL_MODULE_INFO_SYM = { + .tag = HARDWARE_MODULE_TAG, + .version_major = 1, + .version_minor = 0, + .id = BT_HARDWARE_MODULE_ID, + .name = "BlueZ Bluetooth stack", + .author = "Intel Corporation", + .methods = &bluetooth_module_methods +};
diff --git a/android/hal-gatt.c b/android/hal-gatt.c new file mode 100644 index 0000000..f7217c7 --- /dev/null +++ b/android/hal-gatt.c
@@ -0,0 +1,2103 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <string.h> +#include <stdlib.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" +#include "hal-utils.h" + +static const btgatt_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void gatt_id_from_hal(btgatt_gatt_id_t *to, + struct hal_gatt_gatt_id *from) +{ + memcpy(&to->uuid, from->uuid, sizeof(to->uuid)); + to->inst_id = from->inst_id; +} + +static void gatt_id_to_hal(struct hal_gatt_gatt_id *to, btgatt_gatt_id_t *from) +{ + memcpy(to->uuid, &from->uuid, sizeof(from->uuid)); + to->inst_id = from->inst_id; +} + +static void srvc_id_from_hal(btgatt_srvc_id_t *to, + struct hal_gatt_srvc_id *from) +{ + memcpy(&to->id.uuid, from->uuid, sizeof(to->id.uuid)); + to->id.inst_id = from->inst_id; + to->is_primary = from->is_primary; +} + +static void srvc_id_to_hal(struct hal_gatt_srvc_id *to, btgatt_srvc_id_t *from) +{ + memcpy(to->uuid, &from->id.uuid, sizeof(from->id.uuid)); + to->inst_id = from->id.inst_id; + to->is_primary = from->is_primary; +} + +/* Client Event Handlers */ + +static void handle_register_client(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_register_client *ev = buf; + + if (cbs->client->register_client_cb) + cbs->client->register_client_cb(ev->status, ev->client_if, + (bt_uuid_t *) ev->app_uuid); +} + +static void handle_scan_result(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_scan_result *ev = buf; + uint8_t ad[62]; + + if (len != sizeof(*ev) + ev->len) { + error("gatt: invalid scan result event, aborting"); + exit(EXIT_FAILURE); + } + + /* Java assumes that passed data has 62 bytes */ + memset(ad, 0, sizeof(ad)); + memcpy(ad, ev->adv_data, ev->len > sizeof(ad) ? sizeof(ad) : ev->len); + + if (cbs->client->scan_result_cb) + cbs->client->scan_result_cb((bt_bdaddr_t *) ev->bda, ev->rssi, + ad); +} + +static void handle_connect(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_connect *ev = buf; + + if (cbs->client->open_cb) + cbs->client->open_cb(ev->conn_id, ev->status, ev->client_if, + (bt_bdaddr_t *) ev->bda); +} + +static void handle_disconnect(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_disconnect *ev = buf; + + if (cbs->client->close_cb) + cbs->client->close_cb(ev->conn_id, ev->status, ev->client_if, + (bt_bdaddr_t *) ev->bda); +} + +static void handle_search_complete(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_search_complete *ev = buf; + + if (cbs->client->search_complete_cb) + cbs->client->search_complete_cb(ev->conn_id, ev->status); +} + +static void handle_search_result(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_search_result *ev = buf; + btgatt_srvc_id_t srvc_id; + + srvc_id_from_hal(&srvc_id, &ev->srvc_id); + + if (cbs->client->search_result_cb) + cbs->client->search_result_cb(ev->conn_id, &srvc_id); +} + +static void handle_get_characteristic(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_get_characteristic *ev = buf; + btgatt_gatt_id_t char_id; + btgatt_srvc_id_t srvc_id; + + srvc_id_from_hal(&srvc_id, &ev->srvc_id); + gatt_id_from_hal(&char_id, &ev->char_id); + + if (cbs->client->get_characteristic_cb) + cbs->client->get_characteristic_cb(ev->conn_id, ev->status, + &srvc_id, &char_id, + ev->char_prop); +} + +static void handle_get_descriptor(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_get_descriptor *ev = buf; + btgatt_gatt_id_t descr_id; + btgatt_gatt_id_t char_id; + btgatt_srvc_id_t srvc_id; + + srvc_id_from_hal(&srvc_id, &ev->srvc_id); + gatt_id_from_hal(&char_id, &ev->char_id); + gatt_id_from_hal(&descr_id, &ev->descr_id); + + if (cbs->client->get_descriptor_cb) + cbs->client->get_descriptor_cb(ev->conn_id, ev->status, + &srvc_id, &char_id, &descr_id); +} + +static void handle_get_included_service(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_get_inc_service *ev = buf; + btgatt_srvc_id_t srvc_id; + btgatt_srvc_id_t incl_srvc_id; + + srvc_id_from_hal(&srvc_id, &ev->srvc_id); + srvc_id_from_hal(&incl_srvc_id, &ev->incl_srvc_id); + + if (cbs->client->get_included_service_cb) + cbs->client->get_included_service_cb(ev->conn_id, ev->status, + &srvc_id, + &incl_srvc_id); +} + +static void handle_register_for_notification(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_reg_for_notif *ev = buf; + btgatt_gatt_id_t char_id; + btgatt_srvc_id_t srvc_id; + + srvc_id_from_hal(&srvc_id, &ev->srvc_id); + gatt_id_from_hal(&char_id, &ev->char_id); + + if (cbs->client->register_for_notification_cb) + cbs->client->register_for_notification_cb(ev->conn_id, + ev->registered, + ev->status, + &srvc_id, + &char_id); +} + +static void handle_notify(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_notify *ev = buf; + btgatt_notify_params_t params; + + if (len != sizeof(*ev) + ev->len) { + error("gatt: invalid notify event, aborting"); + exit(EXIT_FAILURE); + } + + memset(¶ms, 0, sizeof(params)); + memcpy(params.value, ev->value, ev->len); + memcpy(¶ms.bda, ev->bda, sizeof(params.bda)); + + srvc_id_from_hal(¶ms.srvc_id, &ev->srvc_id); + gatt_id_from_hal(¶ms.char_id, &ev->char_id); + + params.len = ev->len; + params.is_notify = ev->is_notify; + + if (cbs->client->notify_cb) + cbs->client->notify_cb(ev->conn_id, ¶ms); +} + +static void handle_read_characteristic(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_read_characteristic *ev = buf; + btgatt_read_params_t params; + + if (len != sizeof(*ev) + ev->data.len) { + error("gatt: invalid read characteristic event, aborting"); + exit(EXIT_FAILURE); + } + + memset(¶ms, 0, sizeof(params)); + + srvc_id_from_hal(¶ms.srvc_id, &ev->data.srvc_id); + gatt_id_from_hal(¶ms.char_id, &ev->data.char_id); + gatt_id_from_hal(¶ms.descr_id, &ev->data.descr_id); + + memcpy(¶ms.value.value, ev->data.value, ev->data.len); + + params.value_type = ev->data.value_type; + params.value.len = ev->data.len; + params.status = ev->data.status; + + if (cbs->client->read_characteristic_cb) + cbs->client->read_characteristic_cb(ev->conn_id, ev->status, + ¶ms); +} + +static void handle_write_characteristic(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_write_characteristic *ev = buf; + btgatt_write_params_t params; + + memset(¶ms, 0, sizeof(params)); + + srvc_id_from_hal(¶ms.srvc_id, &ev->data.srvc_id); + gatt_id_from_hal(¶ms.char_id, &ev->data.char_id); + gatt_id_from_hal(¶ms.descr_id, &ev->data.descr_id); + + params.status = ev->data.status; + + if (cbs->client->write_characteristic_cb) + cbs->client->write_characteristic_cb(ev->conn_id, ev->status, + ¶ms); +} + +static void handle_read_descriptor(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_read_descriptor *ev = buf; + btgatt_read_params_t params; + + if (len != sizeof(*ev) + ev->data.len) { + error("gatt: invalid read descriptor event, aborting"); + exit(EXIT_FAILURE); + } + + memset(¶ms, 0, sizeof(params)); + + srvc_id_from_hal(¶ms.srvc_id, &ev->data.srvc_id); + gatt_id_from_hal(¶ms.char_id, &ev->data.char_id); + gatt_id_from_hal(¶ms.descr_id, &ev->data.descr_id); + + memcpy(¶ms.value.value, ev->data.value, ev->data.len); + + params.value_type = ev->data.value_type; + params.value.len = ev->data.len; + params.status = ev->data.status; + + if (cbs->client->read_descriptor_cb) + cbs->client->read_descriptor_cb(ev->conn_id, ev->status, + ¶ms); +} + +static void handle_write_descriptor(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_write_descriptor *ev = buf; + btgatt_write_params_t params; + + memset(¶ms, 0, sizeof(params)); + + srvc_id_from_hal(¶ms.srvc_id, &ev->data.srvc_id); + gatt_id_from_hal(¶ms.char_id, &ev->data.char_id); + gatt_id_from_hal(¶ms.descr_id, &ev->data.descr_id); + + params.status = ev->data.status; + + if (cbs->client->write_descriptor_cb) + cbs->client->write_descriptor_cb(ev->conn_id, ev->status, + ¶ms); +} + +static void handle_execute_write(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_exec_write *ev = buf; + + if (cbs->client->execute_write_cb) + cbs->client->execute_write_cb(ev->conn_id, ev->status); +} + +static void handle_read_remote_rssi(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_read_remote_rssi *ev = buf; + + if (cbs->client->read_remote_rssi_cb) + cbs->client->read_remote_rssi_cb(ev->client_if, + (bt_bdaddr_t *) ev->address, + ev->rssi, ev->status); +} + +static void handle_listen(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_client_listen *ev = buf; + + if (cbs->client->listen_cb) + cbs->client->listen_cb(ev->status, ev->server_if); +} + +/* Server Event Handlers */ + +static void handle_register_server(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_register *ev = buf; + + if (cbs->server->register_server_cb) + cbs->server->register_server_cb(ev->status, ev->server_if, + (bt_uuid_t *) &ev->uuid); +} + +static void handle_connection(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_connection *ev = buf; + + if (cbs->server->connection_cb) + cbs->server->connection_cb(ev->conn_id, ev->server_if, + ev->connected, + (bt_bdaddr_t *) &ev->bdaddr); +} + +static void handle_service_added(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_service_added *ev = buf; + btgatt_srvc_id_t srvc_id; + + srvc_id_from_hal(&srvc_id, &ev->srvc_id); + + if (cbs->server->service_added_cb) + cbs->server->service_added_cb(ev->status, ev->server_if, + &srvc_id, ev->srvc_handle); +} + +static void handle_included_service_added(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_inc_srvc_added *ev = buf; + + if (cbs->server->included_service_added_cb) + cbs->server->included_service_added_cb(ev->status, + ev->server_if, + ev->srvc_handle, + ev->incl_srvc_handle); +} + +static void handle_characteristic_added(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_characteristic_added *ev = buf; + + if (cbs->server->characteristic_added_cb) + cbs->server->characteristic_added_cb(ev->status, ev->server_if, + (bt_uuid_t *) &ev->uuid, + ev->srvc_handle, + ev->char_handle); +} + +static void handle_descriptor_added(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_descriptor_added *ev = buf; + + if (cbs->server->descriptor_added_cb) + cbs->server->descriptor_added_cb(ev->status, ev->server_if, + (bt_uuid_t *) &ev->uuid, + ev->srvc_handle, + ev->descr_handle); +} + +static void handle_service_started(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_service_started *ev = buf; + + if (cbs->server->service_started_cb) + cbs->server->service_started_cb(ev->status, ev->server_if, + ev->srvc_handle); +} + +static void handle_service_stopped(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_service_stopped *ev = buf; + + if (cbs->server->service_stopped_cb) + cbs->server->service_stopped_cb(ev->status, ev->server_if, + ev->srvc_handle); +} + +static void handle_service_deleted(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_service_deleted *ev = buf; + + if (cbs->server->service_deleted_cb) + cbs->server->service_deleted_cb(ev->status, ev->server_if, + ev->srvc_handle); +} + +static void handle_request_read(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_request_read *ev = buf; + + if (cbs->server->request_read_cb) + cbs->server->request_read_cb(ev->conn_id, ev->trans_id, + (bt_bdaddr_t *) &ev->bdaddr, + ev->attr_handle, ev->offset, + ev->is_long); +} + +static void handle_request_write(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_request_write *ev = buf; + + if (len != sizeof(*ev) + ev->length) { + error("gatt: invalid request write event, aborting"); + exit(EXIT_FAILURE); + } + + if (cbs->server->request_write_cb) + cbs->server->request_write_cb(ev->conn_id, ev->trans_id, + (bt_bdaddr_t *) ev->bdaddr, + ev->attr_handle, ev->offset, + ev->length, ev->need_rsp, + ev->is_prep, ev->value); +} + +static void handle_request_exec_write(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_request_exec_write *ev = buf; + + if (cbs->server->request_exec_write_cb) + cbs->server->request_exec_write_cb(ev->conn_id, ev->trans_id, + (bt_bdaddr_t *) ev->bdaddr, + ev->exec_write); +} + +static void handle_response_confirmation(void *buf, uint16_t len, int fd) +{ + struct hal_ev_gatt_server_rsp_confirmation *ev = buf; + + if (cbs->server->response_confirmation_cb) + cbs->server->response_confirmation_cb(ev->status, ev->handle); +} + +static void handle_configure_mtu(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_configure_mtu *ev = buf; + + if (cbs->client->configure_mtu_cb) + cbs->client->configure_mtu_cb(ev->conn_id, ev->status, ev->mtu); +#endif +} + +static void handle_filter_config(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_filter_config *ev = buf; + + if (cbs->client->scan_filter_cfg_cb) + cbs->client->scan_filter_cfg_cb(ev->action, ev->client_if, + ev->status, ev->type, + ev->space); +#endif +} + +static void handle_filter_params(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_filter_params *ev = buf; + + if (cbs->client->scan_filter_param_cb) + cbs->client->scan_filter_param_cb(ev->action, ev->client_if, + ev->status, ev->space); +#endif +} + +static void handle_filter_status(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_filter_status *ev = buf; + + if (cbs->client->scan_filter_status_cb) + cbs->client->scan_filter_status_cb(ev->enable, ev->client_if, + ev->status); +#endif +} + +static void handle__multi_adv_enable(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_multi_adv_enable *ev = buf; + + if (cbs->client->multi_adv_enable_cb) + cbs->client->multi_adv_enable_cb(ev->client_if, ev->status); +#endif +} + +static void handle_multi_adv_update(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_multi_adv_update *ev = buf; + + if (cbs->client->multi_adv_update_cb) + cbs->client->multi_adv_update_cb(ev->client_if, ev->status); +#endif +} + +static void handle_multi_adv_data(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_multi_adv_data *ev = buf; + + if (cbs->client->multi_adv_data_cb) + cbs->client->multi_adv_data_cb(ev->client_if, ev->status); +#endif +} + +static void handle_multi_adv_disable(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_multi_adv_disable *ev = buf; + + if (cbs->client->multi_adv_disable_cb) + cbs->client->multi_adv_disable_cb(ev->client_if, ev->status); +#endif +} + +static void handle_client_congestion(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_congestion *ev = buf; + + if (cbs->client->congestion_cb) + cbs->client->congestion_cb(ev->conn_id, ev->congested); +#endif +} + +static void handle_config_batchscan(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_config_batchscan *ev = buf; + + if (cbs->client->batchscan_cfg_storage_cb) + cbs->client->batchscan_cfg_storage_cb(ev->client_if, + ev->status); +#endif +} + +static void handle_enable_batchscan(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_enable_batchscan *ev = buf; + + if (cbs->client->batchscan_enb_disable_cb) + cbs->client->batchscan_enb_disable_cb(ev->action, ev->client_if, + ev->status); +#endif +} + +static void handle_client_batchscan_reports(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_batchscan_reports *ev = buf; + + if (cbs->client->batchscan_reports_cb) + cbs->client->batchscan_reports_cb(ev->client_if, ev->status, + ev->format, ev->num, + ev->data_len, ev->data); +#endif +} + +static void handle_batchscan_threshold(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_batchscan_threshold *ev = buf; + + if (cbs->client->batchscan_threshold_cb) + cbs->client->batchscan_threshold_cb(ev->client_if); +#endif +} + +static void handle_track_adv(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_client_track_adv *ev = buf; + + if (cbs->client->track_adv_event_cb) + cbs->client->track_adv_event_cb(ev->client_if, ev->filetr_index, + ev->address_type, + (bt_bdaddr_t *) ev->address, + ev->state); +#endif +} + +static void handle_indication_send(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_server_indication_sent *ev = buf; + + if (cbs->server->indication_sent_cb) + cbs->server->indication_sent_cb(ev->conn_id, ev->status); +#endif +} + +static void handle_server_congestion(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_gatt_server_congestion *ev = buf; + + if (cbs->server->congestion_cb) + cbs->server->congestion_cb(ev->conn_id, ev->congested); +#endif +} + +static void handle_server_mtu_changed(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 1, 0) + struct hal_ev_gatt_server_mtu_changed *ev = buf; + + if (cbs->server->mtu_changed_cb) + cbs->server->mtu_changed_cb(ev->conn_id, ev->mtu); +#endif +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_GATT_CLIENT_REGISTER_CLIENT */ + { handle_register_client, false, + sizeof(struct hal_ev_gatt_client_register_client) }, + /* HAL_EV_GATT_CLIENT_SCAN_RESULT */ + { handle_scan_result, true, + sizeof(struct hal_ev_gatt_client_scan_result) }, + /* HAL_EV_GATT_CLIENT_CONNECT */ + { handle_connect, false, sizeof(struct hal_ev_gatt_client_connect) }, + /* HAL_EV_GATT_CLIENT_DISCONNECT */ + { handle_disconnect, false, + sizeof(struct hal_ev_gatt_client_disconnect) }, + /* HAL_EV_GATT_CLIENT_SEARCH_COMPLETE */ + { handle_search_complete, false, + sizeof(struct hal_ev_gatt_client_search_complete) }, + /* HAL_EV_GATT_CLIENT_SEARCH_RESULT */ + { handle_search_result, false, + sizeof(struct hal_ev_gatt_client_search_result) }, + /* HAL_EV_GATT_CLIENT_GET_CHARACTERISTIC */ + { handle_get_characteristic, false, + sizeof(struct hal_ev_gatt_client_get_characteristic) }, + /* HAL_EV_GATT_CLIENT_GET_DESCRIPTOR */ + { handle_get_descriptor, false, + sizeof(struct hal_ev_gatt_client_get_descriptor) }, + /* HAL_EV_GATT_CLIENT_GET_INC_SERVICE */ + { handle_get_included_service, false, + sizeof(struct hal_ev_gatt_client_get_inc_service) }, + /* HAL_EV_GATT_CLIENT_REGISTER_FOR_NOTIF */ + { handle_register_for_notification, false, + sizeof(struct hal_ev_gatt_client_reg_for_notif) }, + /* HAL_EV_GATT_CLIENT_NOTIFY */ + { handle_notify, true, sizeof(struct hal_ev_gatt_client_notify) }, + /* HAL_EV_GATT_CLIENT_READ_CHARACTERISTIC */ + { handle_read_characteristic, true, + sizeof(struct hal_ev_gatt_client_read_characteristic) }, + /* HAL_EV_GATT_CLIENT_WRITE_CHARACTERISTIC */ + { handle_write_characteristic, false, + sizeof(struct hal_ev_gatt_client_write_characteristic) }, + /* HAL_EV_GATT_CLIENT_READ_DESCRIPTOR */ + { handle_read_descriptor, true, + sizeof(struct hal_ev_gatt_client_read_descriptor) }, + /* HAL_EV_GATT_CLIENT_WRITE_DESCRIPTOR */ + { handle_write_descriptor, false, + sizeof(struct hal_ev_gatt_client_write_descriptor) }, + /* HAL_EV_GATT_CLIENT_EXEC_WRITE */ + { handle_execute_write, false, + sizeof(struct hal_ev_gatt_client_exec_write) }, + /* HAL_EV_GATT_CLIENT_READ_REMOTE_RSSI */ + { handle_read_remote_rssi, false, + sizeof(struct hal_ev_gatt_client_read_remote_rssi) }, + /* HAL_EV_GATT_CLIENT_LISTEN */ + { handle_listen, false, sizeof(struct hal_ev_gatt_client_listen) }, + /* HAL_EV_GATT_SERVER_REGISTER */ + { handle_register_server, false, + sizeof(struct hal_ev_gatt_server_register) }, + /* HAL_EV_GATT_SERVER_CONNECTION */ + { handle_connection, false, + sizeof(struct hal_ev_gatt_server_connection) }, + /* HAL_EV_GATT_SERVER_SERVICE_ADDED */ + { handle_service_added, false, + sizeof(struct hal_ev_gatt_server_service_added) }, + /* HAL_EV_GATT_SERVER_INC_SRVC_ADDED */ + { handle_included_service_added, false, + sizeof(struct hal_ev_gatt_server_inc_srvc_added) }, + /* HAL_EV_GATT_SERVER_CHAR_ADDED */ + { handle_characteristic_added, false, + sizeof(struct hal_ev_gatt_server_characteristic_added) }, + /* HAL_EV_GATT_SERVER_DESCRIPTOR_ADDED */ + { handle_descriptor_added, false, + sizeof(struct hal_ev_gatt_server_descriptor_added) }, + /* HAL_EV_GATT_SERVER_SERVICE_STARTED */ + { handle_service_started, false, + sizeof(struct hal_ev_gatt_server_service_started) }, + /* HAL_EV_GATT_SERVER_SERVICE_STOPPED */ + { handle_service_stopped, false, + sizeof(struct hal_ev_gatt_server_service_stopped) }, + /* HAL_EV_GATT_SERVER_SERVICE_DELETED */ + { handle_service_deleted, false, + sizeof(struct hal_ev_gatt_server_service_deleted) }, + /* HAL_EV_GATT_SERVER_REQUEST_READ */ + { handle_request_read, false, + sizeof(struct hal_ev_gatt_server_request_read) }, + /* HAL_EV_GATT_SERVER_REQUEST_WRITE */ + { handle_request_write, true, + sizeof(struct hal_ev_gatt_server_request_write) }, + /* HAL_EV_GATT_SERVER_REQUEST_EXEC_WRITE */ + { handle_request_exec_write, false, + sizeof(struct hal_ev_gatt_server_request_exec_write) }, + /* HAL_EV_GATT_SERVER_RSP_CONFIRMATION */ + { handle_response_confirmation, false, + sizeof(struct hal_ev_gatt_server_rsp_confirmation) }, + /* HAL_EV_GATT_CLIENT_CONFIGURE_MTU */ + { handle_configure_mtu, false, + sizeof(struct hal_ev_gatt_client_configure_mtu) }, + /* HAL_EV_GATT_CLIENT_FILTER_CONFIG */ + { handle_filter_config, false, + sizeof(struct hal_ev_gatt_client_filter_config) }, + /* HAL_EV_GATT_CLIENT_FILTER_PARAMS */ + { handle_filter_params, false, + sizeof(struct hal_ev_gatt_client_filter_params) }, + /* HAL_EV_GATT_CLIENT_FILTER_STATUS */ + { handle_filter_status, false, + sizeof(struct hal_ev_gatt_client_filter_status) }, + /* HAL_EV_GATT_CLIENT_MULTI_ADV_ENABLE */ + { handle__multi_adv_enable, false, + sizeof(struct hal_ev_gatt_client_multi_adv_enable) }, + /* HAL_EV_GATT_CLIENT_MULTI_ADV_UPDATE */ + { handle_multi_adv_update, false, + sizeof(struct hal_ev_gatt_client_multi_adv_update) }, + /* HAL_EV_GATT_CLIENT_MULTI_ADV_DATA */ + { handle_multi_adv_data, false, + sizeof(struct hal_ev_gatt_client_multi_adv_data) }, + /* HAL_EV_GATT_CLIENT_MULTI_ADV_DISABLE */ + { handle_multi_adv_disable, false, + sizeof(struct hal_ev_gatt_client_multi_adv_disable) }, + /* HAL_EV_GATT_CLIENT_CONGESTION */ + { handle_client_congestion, false, + sizeof(struct hal_ev_gatt_client_congestion) }, + /* HAL_EV_GATT_CLIENT_CONFIG_BATCHSCAN */ + { handle_config_batchscan, false, + sizeof(struct hal_ev_gatt_client_config_batchscan) }, + /* HAL_EV_GATT_CLIENT_ENABLE_BATCHSCAN */ + { handle_enable_batchscan, false, + sizeof(struct hal_ev_gatt_client_enable_batchscan) }, + /* HAL_EV_GATT_CLIENT_BATCHSCAN_REPORTS */ + { handle_client_batchscan_reports, true, + sizeof(struct hal_ev_gatt_client_batchscan_reports) }, + /* HAL_EV_GATT_CLIENT_BATCHSCAN_THRESHOLD */ + { handle_batchscan_threshold, false, + sizeof(struct hal_ev_gatt_client_batchscan_threshold) }, + /* HAL_EV_GATT_CLIENT_TRACK_ADV */ + { handle_track_adv, false, + sizeof(struct hal_ev_gatt_client_track_adv) }, + /* HAL_EV_GATT_SERVER_INDICATION_SENT */ + { handle_indication_send, false, + sizeof(struct hal_ev_gatt_server_indication_sent) }, + /* HAL_EV_GATT_SERVER_CONGESTION */ + { handle_server_congestion, false, + sizeof(struct hal_ev_gatt_server_congestion) }, + /* HAL_EV_GATT_SERVER_MTU_CHANGED */ + { handle_server_mtu_changed, false, + sizeof(struct hal_ev_gatt_server_mtu_changed) }, + }; + +/* Client API */ + +static bt_status_t register_client(bt_uuid_t *uuid) +{ + struct hal_cmd_gatt_client_register cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.uuid, uuid, sizeof(*uuid)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_REGISTER, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t unregister_client(int client_if) +{ + struct hal_cmd_gatt_client_unregister cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_UNREGISTER, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t scan_real(int client_if, bool start) +{ + struct hal_cmd_gatt_client_scan cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.start = start; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_SCAN, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t scan(bool start) +{ + return scan_real(0, start); +} +#else +static bt_status_t scan(int client_if, bool start) +{ + return scan_real(client_if, start); +} +#endif + +static bt_status_t connect_real(int client_if, const bt_bdaddr_t *bd_addr, + bool is_direct, int transport) +{ + struct hal_cmd_gatt_client_connect cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.is_direct = is_direct; + cmd.transport = transport; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t connect(int client_if, const bt_bdaddr_t *bd_addr, + bool is_direct, int transport) +{ + return connect_real(client_if, bd_addr, is_direct, transport); +} +#else +static bt_status_t connect(int client_if, const bt_bdaddr_t *bd_addr, + bool is_direct) +{ + return connect_real(client_if, bd_addr, is_direct, + BT_TRANSPORT_UNKNOWN); +} +#endif + +static bt_status_t disconnect(int client_if, const bt_bdaddr_t *bd_addr, + int conn_id) +{ + struct hal_cmd_gatt_client_disconnect cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.conn_id = conn_id; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_DISCONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t listen(int client_if, bool start) +{ + struct hal_cmd_gatt_client_listen cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.start = start; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_LISTEN, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t refresh(int client_if, const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_gatt_client_refresh cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_REFRESH, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t search_service(int conn_id, bt_uuid_t *filter_uuid) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_search_service *cmd = (void *) buf; + size_t len = sizeof(*cmd); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memset(cmd, 0, sizeof(*cmd)); + + cmd->conn_id = conn_id; + + if (filter_uuid) { + memcpy(cmd->filter_uuid, filter_uuid, sizeof(*filter_uuid)); + len += sizeof(*filter_uuid); + cmd->filtered = 1; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SEARCH_SERVICE, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t get_included_service(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_srvc_id_t *start_incl_srvc_id) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_get_included_service *cmd = (void *) buf; + size_t len = sizeof(*cmd); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->conn_id = conn_id; + + srvc_id_to_hal(&cmd->srvc_id, srvc_id); + cmd->continuation = 0; + + if (start_incl_srvc_id) { + srvc_id_to_hal(&cmd->incl_srvc_id[0], start_incl_srvc_id); + len += sizeof(cmd->incl_srvc_id[0]); + cmd->continuation = 1; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_INCLUDED_SERVICE, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t get_characteristic(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *start_char_id) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_get_characteristic *cmd = (void *) buf; + size_t len = sizeof(*cmd); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->conn_id = conn_id; + + srvc_id_to_hal(&cmd->srvc_id, srvc_id); + cmd->continuation = 0; + + if (start_char_id) { + gatt_id_to_hal(&cmd->char_id[0], start_char_id); + len += sizeof(cmd->char_id[0]); + cmd->continuation = 1; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_CHARACTERISTIC, + len, cmd, NULL, NULL, NULL); +} + +static bt_status_t get_descriptor(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *start_descr_id) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_get_descriptor *cmd = (void *) buf; + size_t len = sizeof(*cmd); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->conn_id = conn_id; + + srvc_id_to_hal(&cmd->srvc_id, srvc_id); + gatt_id_to_hal(&cmd->char_id, char_id); + cmd->continuation = 0; + + if (start_descr_id) { + gatt_id_to_hal(&cmd->descr_id[0], start_descr_id); + len += sizeof(cmd->descr_id[0]); + cmd->continuation = 1; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_DESCRIPTOR, + len, cmd, NULL , NULL, NULL); +} + +static bt_status_t read_characteristic(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id, + int auth_req) +{ + struct hal_cmd_gatt_client_read_characteristic cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.conn_id = conn_id; + cmd.auth_req = auth_req; + + srvc_id_to_hal(&cmd.srvc_id, srvc_id); + gatt_id_to_hal(&cmd.char_id, char_id); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_CHARACTERISTIC, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t write_characteristic(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id, + int write_type, int len, int auth_req, + char *p_value) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_write_characteristic *cmd = (void *) buf; + size_t cmd_len = sizeof(*cmd) + len; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->conn_id = conn_id; + cmd->write_type = write_type; + cmd->len = len; + cmd->auth_req = auth_req; + + srvc_id_to_hal(&cmd->srvc_id, srvc_id); + gatt_id_to_hal(&cmd->char_id, char_id); + + memcpy(cmd->value, p_value, len); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_WRITE_CHARACTERISTIC, + cmd_len, cmd, NULL, NULL, NULL); +} + +static bt_status_t read_descriptor(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *descr_id, + int auth_req) +{ + struct hal_cmd_gatt_client_read_descriptor cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.conn_id = conn_id; + cmd.auth_req = auth_req; + + srvc_id_to_hal(&cmd.srvc_id, srvc_id); + gatt_id_to_hal(&cmd.char_id, char_id); + gatt_id_to_hal(&cmd.descr_id, descr_id); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_DESCRIPTOR, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t write_descriptor(int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *descr_id, + int write_type, int len, int auth_req, + char *p_value) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_write_descriptor *cmd = (void *) buf; + size_t cmd_len = sizeof(*cmd) + len; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->conn_id = conn_id; + cmd->write_type = write_type; + cmd->len = len; + cmd->auth_req = auth_req; + + srvc_id_to_hal(&cmd->srvc_id, srvc_id); + gatt_id_to_hal(&cmd->char_id, char_id); + gatt_id_to_hal(&cmd->descr_id, descr_id); + + memcpy(cmd->value, p_value, len); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_WRITE_DESCRIPTOR, + cmd_len, cmd, NULL, NULL, NULL); +} + +static bt_status_t execute_write(int conn_id, int execute) +{ + struct hal_cmd_gatt_client_execute_write cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.conn_id = conn_id; + cmd.execute = execute; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_EXECUTE_WRITE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t register_for_notification(int client_if, + const bt_bdaddr_t *bd_addr, + btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id) +{ + struct hal_cmd_gatt_client_register_for_notification cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + srvc_id_to_hal(&cmd.srvc_id, srvc_id); + gatt_id_to_hal(&cmd.char_id, char_id); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_REGISTER_FOR_NOTIFICATION, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t deregister_for_notification(int client_if, + const bt_bdaddr_t *bd_addr, + btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id) +{ + struct hal_cmd_gatt_client_deregister_for_notification cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + srvc_id_to_hal(&cmd.srvc_id, srvc_id); + gatt_id_to_hal(&cmd.char_id, char_id); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DEREGISTER_FOR_NOTIFICATION, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t read_remote_rssi(int client_if, const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_gatt_client_read_remote_rssi cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_REMOTE_RSSI, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int get_device_type(const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_gatt_client_get_device_type cmd; + uint8_t dev_type; + size_t resp_len = sizeof(dev_type); + bt_status_t status; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + status = hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_GET_DEVICE_TYPE, + sizeof(cmd), &cmd, &resp_len, &dev_type, NULL); + + if (status != BT_STATUS_SUCCESS || resp_len != sizeof(dev_type)) + return 0; + + return dev_type; +} + +static bt_status_t set_adv_data_real(int server_if, bool set_scan_rsp, + bool include_name, bool include_txpower, + int min_interval, int max_interval, + int appearance, uint16_t manufacturer_len, + char *manufacturer_data, + uint16_t service_data_len, char *service_data, + uint16_t service_uuid_len, char *service_uuid) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_set_adv_data *cmd = (void *) buf; + size_t cmd_len; + uint8_t *data; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd_len = sizeof(*cmd) + manufacturer_len + service_data_len + + service_uuid_len; + + if (cmd_len > IPC_MTU) + return BT_STATUS_FAIL; + + cmd->server_if = server_if; + cmd->set_scan_rsp = set_scan_rsp; + cmd->include_name = include_name; + cmd->include_txpower = include_txpower; + cmd->min_interval = min_interval; + cmd->max_interval = max_interval; + cmd->appearance = appearance; + cmd->manufacturer_len = manufacturer_len; + cmd->service_data_len = service_data_len; + cmd->service_uuid_len = service_uuid_len; + + data = cmd->data; + + if (manufacturer_data && manufacturer_len) { + memcpy(data, manufacturer_data, manufacturer_len); + data += manufacturer_len; + } + + if (service_data && service_data_len) { + memcpy(data, service_data, service_data_len); + data += service_data_len; + } + + if (service_uuid && service_uuid_len) + memcpy(data, service_uuid, service_uuid_len); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_SET_ADV_DATA, + cmd_len, cmd, NULL, NULL, NULL); +} + +/* + * This is temporary solution and support for older Android versions might + * be removed at any time. + */ +#if ANDROID_VERSION < PLATFORM_VER(4, 4, 3) +static bt_status_t set_adv_data(int server_if, bool set_scan_rsp, + bool include_name, bool include_txpower, + int min_interval, int max_interval, + int appearance, uint16_t manufacturer_len, + char *manufacturer_data) +{ + return set_adv_data_real(server_if, set_scan_rsp, include_name, + include_txpower, min_interval, + max_interval, appearance, + manufacturer_len, manufacturer_data, + 0, NULL, 0, NULL); +} +#else +static bt_status_t set_adv_data(int server_if, bool set_scan_rsp, + bool include_name, bool include_txpower, + int min_interval, int max_interval, + int appearance, uint16_t manufacturer_len, + char *manufacturer_data, + uint16_t service_data_len, char *service_data, + uint16_t service_uuid_len, char *service_uuid) +{ + return set_adv_data_real(server_if, set_scan_rsp, include_name, + include_txpower, min_interval, + max_interval, appearance, + manufacturer_len, manufacturer_data, + service_data_len, service_data, + service_uuid_len, service_uuid); +} +#endif + +static bt_status_t test_command(int command, btgatt_test_params_t *params) +{ + struct hal_cmd_gatt_client_test_command cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.command = command; + + memcpy(cmd.bda1, params->bda1, sizeof(*params->bda1)); + memcpy(cmd.uuid1, params->uuid1, sizeof(*params->uuid1)); + + cmd.u1 = params->u1; + cmd.u2 = params->u2; + cmd.u3 = params->u3; + cmd.u4 = params->u4; + cmd.u5 = params->u5; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_TEST_COMMAND, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t scan_filter_param_setup(int client_if, int action, + int filt_index, int feat_seln, + int list_logic_type, + int filt_logic_type, + int rssi_high_thres, + int rssi_low_thres, + int dely_mode, + int found_timeout, + int lost_timeout, + int found_timeout_cnt) +{ + struct hal_cmd_gatt_client_scan_filter_setup cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.action = action; + cmd.filter_index = filt_index; + cmd.features = feat_seln; + cmd.list_type = list_logic_type; + cmd.filter_type = filt_logic_type; + cmd.rssi_hi = rssi_high_thres; + cmd.rssi_lo = rssi_low_thres; + cmd.delivery_mode = dely_mode; + cmd.found_timeout = found_timeout; + cmd.lost_timeout = lost_timeout; + cmd.found_timeout_cnt = found_timeout_cnt; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_SETUP, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t scan_filter_add_remove(int client_if, int action, + int filt_type, int filt_index, + int company_id, + int company_id_mask, + const bt_uuid_t *p_uuid, + const bt_uuid_t *p_uuid_mask, + const bt_bdaddr_t *bd_addr, + char addr_type, + int data_len, char *p_data, + int mask_len, char *p_mask) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_scan_filter_add_remove *cmd = (void *) buf; + size_t cmd_len; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!p_uuid || !p_uuid_mask || !bd_addr) + return BT_STATUS_PARM_INVALID; + + cmd_len = sizeof(*cmd) + data_len + mask_len; + if (cmd_len > IPC_MTU) + return BT_STATUS_FAIL; + + cmd->client_if = client_if; + cmd->action = action; + cmd->filter_type = filt_type; + cmd->filter_index = filt_index; + cmd->company_id = company_id; + cmd->company_id_mask = company_id_mask; + memcpy(cmd->uuid, p_uuid, sizeof(*p_uuid)); + memcpy(cmd->uuid_mask, p_uuid_mask, sizeof(*p_uuid_mask)); + memcpy(cmd->address, bd_addr, sizeof(*bd_addr)); + cmd->address_type = addr_type; + + cmd->data_len = data_len; + memcpy(cmd->data_mask, p_data, data_len); + + cmd->mask_len = mask_len; + memcpy(cmd->data_mask + data_len, p_mask, mask_len); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_ADD_REMOVE, + cmd_len, cmd, NULL, NULL, NULL); +} + +static bt_status_t scan_filter_clear(int client_if, int filt_index) +{ + struct hal_cmd_gatt_client_scan_filter_clear cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.index = filt_index; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_CLEAR, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t scan_filter_enable(int client_if, bool enable) +{ + struct hal_cmd_gatt_client_scan_filter_enable cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.enable = enable; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SCAN_FILTER_ENABLE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t configure_mtu(int conn_id, int mtu) +{ + struct hal_cmd_gatt_client_configure_mtu cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.conn_id = conn_id; + cmd.mtu = mtu; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_CONFIGURE_MTU, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t conn_parameter_update(const bt_bdaddr_t *bd_addr, + int min_interval, + int max_interval, int latency, + int timeout) +{ + struct hal_cmd_gatt_client_conn_param_update cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.address, bd_addr, sizeof(*bd_addr)); + cmd.min_interval = min_interval; + cmd.max_interval = max_interval; + cmd.latency = latency; + cmd.timeout = timeout; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_CONN_PARAM_UPDATE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t set_scan_parameters(int scan_interval, int scan_window) +{ + struct hal_cmd_gatt_client_set_scan_param cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.interval = scan_interval; + cmd.window = scan_window; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SET_SCAN_PARAM, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t multi_adv_enable(int client_if, int min_interval, + int max_interval, int adv_type, + int chnl_map, int tx_power, + int timeout_s) +{ + struct hal_cmd_gatt_client_setup_multi_adv cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.min_interval = min_interval; + cmd.max_interval = max_interval; + cmd.type = adv_type; + cmd.channel_map = chnl_map; + cmd.tx_power = tx_power; + cmd.timeout = timeout_s; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t multi_adv_update(int client_if, int min_interval, + int max_interval, int adv_type, + int chnl_map, int tx_power, + int timeout_s) +{ + struct hal_cmd_gatt_client_update_multi_adv cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.min_interval = min_interval; + cmd.max_interval = max_interval; + cmd.type = adv_type; + cmd.channel_map = chnl_map; + cmd.tx_power = tx_power; + cmd.timeout = timeout_s; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_UPDATE_MULTI_ADV, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t multi_adv_set_inst_data(int client_if, bool set_scan_rsp, + bool include_name, + bool incl_txpower, + int appearance, + int manufacturer_len, + char *manufacturer_data, + int service_data_len, + char *service_data, + int service_uuid_len, + char *service_uuid) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_client_setup_multi_adv_inst *cmd = (void *) buf; + int off = 0; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (manufacturer_len > 0 && !manufacturer_data) + return BT_STATUS_PARM_INVALID; + + if (service_data_len > 0 && !service_data) + return BT_STATUS_PARM_INVALID; + + if (service_uuid_len > 0 && !service_uuid) + return BT_STATUS_PARM_INVALID; + + if (sizeof(*cmd) + manufacturer_len + service_data_len + + service_uuid_len > IPC_MTU) + return BT_STATUS_FAIL; + + cmd->client_if = client_if; + cmd->set_scan_rsp = set_scan_rsp; + cmd->include_name = include_name; + cmd->include_tx_power = incl_txpower; + cmd->appearance = appearance; + cmd->manufacturer_data_len = manufacturer_len; + cmd->service_data_len = service_data_len; + cmd->service_uuid_len = service_uuid_len; + + if (manufacturer_len > 0) { + memcpy(cmd->data_service_uuid, manufacturer_data, + manufacturer_len); + off += manufacturer_len; + } + + if (service_data_len > 0) { + memcpy(cmd->data_service_uuid + off, service_data, + service_data_len); + off += service_data_len; + } + + if (service_uuid_len > 0) { + memcpy(cmd->data_service_uuid + off, service_uuid, + service_uuid_len); + off += service_uuid_len; + } + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV_INST, + sizeof(*cmd) + off, cmd, NULL, NULL, NULL); +} + +static bt_status_t multi_adv_disable(int client_if) +{ + struct hal_cmd_gatt_client_disable_multi_adv_inst cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DISABLE_MULTI_ADV_INST, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t batchscan_cfg_storage(int client_if, int batch_scan_full_max, + int batch_scan_trunc_max, + int batch_scan_notify_threshold) +{ + struct hal_cmd_gatt_client_configure_batchscan cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.full_max = batch_scan_full_max; + cmd.trunc_max = batch_scan_trunc_max; + cmd.notify_threshold = batch_scan_notify_threshold; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_CONFIGURE_BATCHSCAN, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t batchscan_enb_batch_scan(int client_if, int scan_mode, + int scan_interval, + int scan_window, int addr_type, + int discard_rule) +{ + struct hal_cmd_gatt_client_enable_batchscan cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.mode = scan_mode; + cmd.interval = scan_interval; + cmd.window = scan_window; + cmd.address_type = addr_type; + cmd.discard_rule = discard_rule; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_ENABLE_BATCHSCAN, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t batchscan_dis_batch_scan(int client_if) +{ + struct hal_cmd_gatt_client_disable_batchscan cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DISABLE_BATCHSCAN, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t batchscan_read_reports(int client_if, int scan_mode) +{ + struct hal_cmd_gatt_client_read_batchscan_reports cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.client_if = client_if; + cmd.scan_mode = scan_mode; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_READ_BATCHSCAN_REPORTS, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} +#endif + +/* Server API */ + +static bt_status_t register_server(bt_uuid_t *uuid) +{ + struct hal_cmd_gatt_server_register cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.uuid, uuid, sizeof(*uuid)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_REGISTER, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t unregister_server(int server_if) +{ + struct hal_cmd_gatt_server_unregister cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_UNREGISTER, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t server_connect_real(int server_if, + const bt_bdaddr_t *bd_addr, + bool is_direct, int transport) +{ + struct hal_cmd_gatt_server_connect cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.is_direct = is_direct; + cmd.transport = transport; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t server_connect(int server_if, const bt_bdaddr_t *bd_addr, + bool is_direct, int transport) +{ + return server_connect_real(server_if, bd_addr, is_direct, transport); +} +#else +static bt_status_t server_connect(int server_if, const bt_bdaddr_t *bd_addr, + bool is_direct) +{ + return server_connect_real(server_if, bd_addr, is_direct, + BT_TRANSPORT_UNKNOWN); +} +#endif + +static bt_status_t server_disconnect(int server_if, const bt_bdaddr_t *bd_addr, + int conn_id) +{ + struct hal_cmd_gatt_server_disconnect cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.conn_id = conn_id; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_DISCONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t add_service(int server_if, btgatt_srvc_id_t *srvc_id, + int num_handles) +{ + struct hal_cmd_gatt_server_add_service cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.num_handles = num_handles; + + srvc_id_to_hal(&cmd.srvc_id, srvc_id); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_ADD_SERVICE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t add_included_service(int server_if, int service_handle, + int included_handle) +{ + struct hal_cmd_gatt_server_add_inc_service cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.service_handle = service_handle; + cmd.included_handle = included_handle; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_INC_SERVICE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t add_characteristic(int server_if, int service_handle, + bt_uuid_t *uuid, int properties, + int permissions) +{ + struct hal_cmd_gatt_server_add_characteristic cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.service_handle = service_handle; + cmd.properties = properties; + cmd.permissions = permissions; + + memcpy(cmd.uuid, uuid, sizeof(*uuid)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_CHARACTERISTIC, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t add_descriptor(int server_if, int service_handle, + bt_uuid_t *uuid, int permissions) +{ + struct hal_cmd_gatt_server_add_descriptor cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.service_handle = service_handle; + cmd.permissions = permissions; + + memcpy(cmd.uuid, uuid, sizeof(*uuid)); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_ADD_DESCRIPTOR, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t start_service_real(int server_if, int service_handle, + int transport) +{ + struct hal_cmd_gatt_server_start_service cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.service_handle = service_handle; + cmd.transport = transport; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_START_SERVICE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t start_service(int server_if, int service_handle, + int transport) +{ + return start_service_real(server_if, service_handle, transport); +} +#else +static bt_status_t start_service(int server_if, int service_handle, + int transport) +{ + int transport_mask = 0; + + /* Android 5 changes transport enum to bit mask. */ + switch (transport) { + case 0: + transport_mask = GATT_SERVER_TRANSPORT_LE_BIT; + break; + case 1: + transport_mask = GATT_SERVER_TRANSPORT_BREDR_BIT; + break; + case 2: + transport_mask = GATT_SERVER_TRANSPORT_LE_BIT | + GATT_SERVER_TRANSPORT_BREDR_BIT; + break; + } + + return start_service_real(server_if, service_handle, transport_mask); +} +#endif + +static bt_status_t stop_service(int server_if, int service_handle) +{ + struct hal_cmd_gatt_server_stop_service cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.service_handle = service_handle; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, HAL_OP_GATT_SERVER_STOP_SERVICE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t delete_service(int server_if, int service_handle) +{ + struct hal_cmd_gatt_server_delete_service cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.server_if = server_if; + cmd.service_handle = service_handle; + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_DELETE_SERVICE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t send_indication(int server_if, int attribute_handle, + int conn_id, int len, int confirm, + char *p_value) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_server_send_indication *cmd = (void *) buf; + size_t cmd_len = sizeof(*cmd) + len; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->server_if = server_if; + cmd->attribute_handle = attribute_handle; + cmd->conn_id = conn_id; + cmd->len = len; + cmd->confirm = confirm; + + memcpy(cmd->value, p_value, len); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_SEND_INDICATION, + cmd_len, cmd, NULL, NULL, NULL); +} + +static bt_status_t send_response(int conn_id, int trans_id, int status, + btgatt_response_t *response) +{ + char buf[IPC_MTU]; + struct hal_cmd_gatt_server_send_response *cmd = (void *) buf; + size_t cmd_len = sizeof(*cmd) + sizeof(*response); + + memset(buf, 0 , IPC_MTU); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->conn_id = conn_id; + cmd->trans_id = trans_id; + cmd->status = status; + cmd->handle = response->attr_value.handle; + cmd->offset = response->attr_value.offset; + cmd->auth_req = response->attr_value.auth_req; + cmd->len = response->attr_value.len; + + memcpy(cmd->data, response->attr_value.value, cmd->len); + + return hal_ipc_cmd(HAL_SERVICE_ID_GATT, + HAL_OP_GATT_SERVER_SEND_RESPONSE, + cmd_len, cmd, NULL, NULL, NULL); +} + +static bt_status_t init(const btgatt_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_GATT, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_GATT; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_GATT); + } + + return ret; +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_GATT; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_GATT); + + cbs = NULL; +} + +static btgatt_client_interface_t client_iface = { + .register_client = register_client, + .unregister_client = unregister_client, + .scan = scan, + .connect = connect, + .disconnect = disconnect, + .listen = listen, + .refresh = refresh, + .search_service = search_service, + .get_included_service = get_included_service, + .get_characteristic = get_characteristic, + .get_descriptor = get_descriptor, + .read_characteristic = read_characteristic, + .write_characteristic = write_characteristic, + .read_descriptor = read_descriptor, + .write_descriptor = write_descriptor, + .execute_write = execute_write, + .register_for_notification = register_for_notification, + .deregister_for_notification = deregister_for_notification, + .read_remote_rssi = read_remote_rssi, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .scan_filter_param_setup = scan_filter_param_setup, + .scan_filter_add_remove = scan_filter_add_remove, + .scan_filter_clear = scan_filter_clear, + .scan_filter_enable = scan_filter_enable, +#endif + .get_device_type = get_device_type, + .set_adv_data = set_adv_data, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .configure_mtu = configure_mtu, + .conn_parameter_update = conn_parameter_update, + .set_scan_parameters = set_scan_parameters, + .multi_adv_enable = multi_adv_enable, + .multi_adv_update = multi_adv_update, + .multi_adv_set_inst_data = multi_adv_set_inst_data, + .multi_adv_disable = multi_adv_disable, + .batchscan_cfg_storage = batchscan_cfg_storage, + .batchscan_enb_batch_scan = batchscan_enb_batch_scan, + .batchscan_dis_batch_scan = batchscan_dis_batch_scan, + .batchscan_read_reports = batchscan_read_reports, +#endif + .test_command = test_command, +}; + +static btgatt_server_interface_t server_iface = { + .register_server = register_server, + .unregister_server = unregister_server, + .connect = server_connect, + .disconnect = server_disconnect, + .add_service = add_service, + .add_included_service = add_included_service, + .add_characteristic = add_characteristic, + .add_descriptor = add_descriptor, + .start_service = start_service, + .stop_service = stop_service, + .delete_service = delete_service, + .send_indication = send_indication, + .send_response = send_response, +}; + +static btgatt_interface_t iface = { + .size = sizeof(iface), + .init = init, + .cleanup = cleanup, + .client = &client_iface, + .server = &server_iface, +}; + +btgatt_interface_t *bt_get_gatt_interface(void) +{ + return &iface; +}
diff --git a/android/hal-handsfree-client.c b/android/hal-handsfree-client.c new file mode 100644 index 0000000..93b5746 --- /dev/null +++ b/android/hal-handsfree-client.c
@@ -0,0 +1,653 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +#include <cutils/properties.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" + +static const bthf_client_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_conn_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_conn_state *ev = buf; + + if (cbs->connection_state_cb) + cbs->connection_state_cb(ev->state, ev->peer_feat, + ev->chld_feat, + (bt_bdaddr_t *) ev->bdaddr); +} + +static void handle_audio_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_audio_state *ev = buf; + + if (cbs->audio_state_cb) + cbs->audio_state_cb(ev->state, (bt_bdaddr_t *) (ev->bdaddr)); +} + +static void handle_vr_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_vr_state *ev = buf; + + if (cbs->vr_cmd_cb) + cbs->vr_cmd_cb(ev->state); +} + +static void handle_network_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_net_state *ev = buf; + + if (cbs->network_state_cb) + cbs->network_state_cb(ev->state); +} + +static void handle_network_roaming(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_net_roaming_type *ev = buf; + + if (cbs->network_roaming_cb) + cbs->network_roaming_cb(ev->state); +} + +static void handle_network_signal(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_net_signal_strength *ev = buf; + + if (cbs->network_signal_cb) + cbs->network_signal_cb(ev->signal_strength); +} + +static void handle_battery_level(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_battery_level *ev = buf; + + if (cbs->battery_level_cb) + cbs->battery_level_cb(ev->battery_level); +} + +static void handle_operator_name(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_operator_name *ev = buf; + uint16_t name_len = ev->name_len; + char *name = NULL; + + if (len != sizeof(*ev) + name_len || + (name_len != 0 && ev->name[name_len - 1] != '\0')) { + error("invalid operator name, aborting"); + exit(EXIT_FAILURE); + } + + if (name_len) + name = (char *) ev->name; + + if (cbs->current_operator_cb) + cbs->current_operator_cb(name); +} + +static void handle_call(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_call_indicator *ev = buf; + + if (cbs->call_cb) + cbs->call_cb(ev->call); +} + +static void handle_call_setup(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_call_setup_indicator *ev = buf; + + if (cbs->callsetup_cb) + cbs->callsetup_cb(ev->call_setup); +} + +static void handle_call_held(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_call_held_indicator *ev = buf; + + if (cbs->callheld_cb) + cbs->callheld_cb(ev->call_held); +} + +static void handle_response_and_hold(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_response_and_hold_status *ev = buf; + + if (cbs->resp_and_hold_cb) + cbs->resp_and_hold_cb(ev->status); +} + +static void handle_clip(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_calling_line_ident *ev = buf; + uint16_t num_len = ev->number_len; + char *number = NULL; + + if (len != sizeof(*ev) + num_len || + (num_len != 0 && ev->number[num_len - 1] != '\0')) { + error("invalid clip, aborting"); + exit(EXIT_FAILURE); + } + + if (num_len) + number = (char *) ev->number; + + if (cbs->clip_cb) + cbs->clip_cb(number); +} + +static void handle_call_waiting(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_call_waiting *ev = buf; + uint16_t num_len = ev->number_len; + char *number = NULL; + + if (len != sizeof(*ev) + num_len || + (num_len != 0 && ev->number[num_len - 1] != '\0')) { + error("invalid call waiting, aborting"); + exit(EXIT_FAILURE); + } + + if (num_len) + number = (char *) ev->number; + + if (cbs->call_waiting_cb) + cbs->call_waiting_cb(number); +} + +static void handle_current_calls(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_current_call *ev = buf; + uint16_t num_len = ev->number_len; + char *number = NULL; + + if (len != sizeof(*ev) + num_len || + (num_len != 0 && ev->number[num_len - 1] != '\0')) { + error("invalid current calls, aborting"); + exit(EXIT_FAILURE); + } + + if (num_len) + number = (char *) ev->number; + + if (cbs->current_calls_cb) + cbs->current_calls_cb(ev->index, ev->direction, ev->call_state, + ev->multiparty, number); +} + +static void handle_volume_change(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_volume_changed *ev = buf; + + if (cbs->volume_change_cb) + cbs->volume_change_cb(ev->type, ev->volume); +} + +static void handle_command_cmp(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_command_complete *ev = buf; + + if (cbs->cmd_complete_cb) + cbs->cmd_complete_cb(ev->type, ev->cme); +} + +static void handle_subscriber_info(void *buf, uint16_t len, int fd) +{ + const struct hal_ev_hf_client_subscriber_service_info *ev = buf; + uint16_t name_len = ev->name_len; + char *name = NULL; + + if (len != sizeof(*ev) + name_len || + (name_len != 0 && ev->name[name_len - 1] != '\0')) { + error("invalid sunscriber info, aborting"); + exit(EXIT_FAILURE); + } + + if (name_len) + name = (char *) ev->name; + + if (cbs->subscriber_info_cb) + cbs->subscriber_info_cb(name, ev->type); +} + +static void handle_in_band_ringtone(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hf_client_inband_settings *ev = buf; + + if (cbs->in_band_ring_tone_cb) + cbs->in_band_ring_tone_cb(ev->state); +} + +static void handle_last_voice_tag_number(void *buf, uint16_t len, int fd) +{ + const struct hal_ev_hf_client_last_void_call_tag_num *ev = buf; + char *number = NULL; + uint16_t num_len = ev->number_len; + + if (len != sizeof(*ev) + num_len || + (num_len != 0 && ev->number[num_len - 1] != '\0')) { + error("invalid voice tag, aborting"); + exit(EXIT_FAILURE); + } + + if (num_len) + number = (char *) ev->number; + + if (cbs->last_voice_tag_number_callback) + cbs->last_voice_tag_number_callback(number); +} + +static void handle_ring_indication(void *buf, uint16_t len, int fd) +{ + if (cbs->ring_indication_cb) + cbs->ring_indication_cb(); +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_HF_CLIENT_CONN_STATE */ + { handle_conn_state, false, + sizeof(struct hal_ev_hf_client_conn_state) }, + /* HAL_EV_HF_CLIENT_AUDIO_STATE */ + { handle_audio_state, false, + sizeof(struct hal_ev_hf_client_audio_state) }, + /* HAL_EV_HF_CLIENT_VR_STATE */ + { handle_vr_state, false, sizeof(struct hal_ev_hf_client_vr_state) }, + /*HAL_EV_HF_CLIENT_NET_STATE */ + { handle_network_state, false, + sizeof(struct hal_ev_hf_client_net_state)}, + /*HAL_EV_HF_CLIENT_NET_ROAMING_TYPE */ + { handle_network_roaming, false, + sizeof(struct hal_ev_hf_client_net_roaming_type) }, + /* HAL_EV_HF_CLIENT_NET_SIGNAL_STRENGTH */ + { handle_network_signal, false, + sizeof(struct hal_ev_hf_client_net_signal_strength) }, + /* HAL_EV_HF_CLIENT_BATTERY_LEVEL */ + { handle_battery_level, false, + sizeof(struct hal_ev_hf_client_battery_level) }, + /* HAL_EV_HF_CLIENT_OPERATOR_NAME */ + { handle_operator_name, true, + sizeof(struct hal_ev_hf_client_operator_name) }, + /* HAL_EV_HF_CLIENT_CALL_INDICATOR */ + { handle_call, false, + sizeof(struct hal_ev_hf_client_call_indicator) }, + /* HAL_EV_HF_CLIENT_CALL_SETUP_INDICATOR */ + { handle_call_setup, false, + sizeof(struct hal_ev_hf_client_call_setup_indicator) }, + /* HAL_EV_HF_CLIENT_CALL_HELD_INDICATOR */ + { handle_call_held, false, + sizeof(struct hal_ev_hf_client_call_held_indicator) }, + /* HAL_EV_HF_CLIENT_RESPONSE_AND_HOLD_STATUS */ + { handle_response_and_hold, false, + sizeof(struct hal_ev_hf_client_response_and_hold_status) }, + /* HAL_EV_HF_CLIENT_CALLING_LINE_IDENT */ + { handle_clip, true, + sizeof(struct hal_ev_hf_client_calling_line_ident) }, + /* HAL_EV_HF_CLIENT_CALL_WAITING */ + { handle_call_waiting, true, + sizeof(struct hal_ev_hf_client_call_waiting) }, + /* HAL_EV_HF_CLIENT_CURRENT_CALL */ + { handle_current_calls, true, + sizeof(struct hal_ev_hf_client_current_call) }, + /* HAL_EV_CLIENT_VOLUME_CHANGED */ + { handle_volume_change, false, + sizeof(struct hal_ev_hf_client_volume_changed) }, + /* HAL_EV_CLIENT_COMMAND_COMPLETE */ + { handle_command_cmp, false, + sizeof(struct hal_ev_hf_client_command_complete) }, + /* HAL_EV_CLIENT_SUBSCRIBER_SERVICE_INFO */ + { handle_subscriber_info, true, + sizeof(struct hal_ev_hf_client_subscriber_service_info) }, + /* HAL_EV_CLIENT_INBAND_SETTINGS */ + { handle_in_band_ringtone, false, + sizeof(struct hal_ev_hf_client_inband_settings) }, + /* HAL_EV_CLIENT_LAST_VOICE_CALL_TAG_NUM */ + { handle_last_voice_tag_number, true, + sizeof(struct hal_ev_hf_client_last_void_call_tag_num) }, + /* HAL_EV_CLIENT_RING_INDICATION */ + { handle_ring_indication, false, 0 }, +}; + +static bt_status_t init(bthf_client_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_HANDSFREE_CLIENT, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_HANDSFREE_CLIENT; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_HANDSFREE_CLIENT); + } + + return ret; +} + +static bt_status_t hf_client_connect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hf_client_connect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT, sizeof(cmd), &cmd, + NULL, NULL, NULL); +} + +static bt_status_t disconnect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hf_client_disconnect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT, sizeof(cmd), &cmd, + NULL, NULL, NULL); +} + +static bt_status_t connect_audio(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hf_client_connect_audio cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT_AUDIO, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +static bt_status_t disconnect_audio(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hf_client_disconnect_audio cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT_AUDIO, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +static bt_status_t start_voice_recognition(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_START_VR, 0, NULL, NULL, NULL, + NULL); +} + +static bt_status_t stop_voice_recognition(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_STOP_VR, 0, NULL, NULL, NULL, + NULL); +} + +static bt_status_t volume_control(bthf_client_volume_type_t type, + int volume) +{ + struct hal_cmd_hf_client_volume_control cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.type = type; + cmd.volume = volume; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_VOLUME_CONTROL, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +static bt_status_t dial(const char *number) +{ + char buf[IPC_MTU]; + struct hal_cmd_hf_client_dial *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (number) { + cmd->number_len = strlen(number) + 1; + memcpy(cmd->number, number, cmd->number_len); + } else { + cmd->number_len = 0; + } + + len = sizeof(*cmd) + cmd->number_len; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DIAL, len, cmd, NULL, NULL, + NULL); +} + +static bt_status_t dial_memory(int location) +{ + struct hal_cmd_hf_client_dial_memory cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.location = location; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DIAL_MEMORY, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t call_action(bthf_client_call_action_t action, int index) +{ + struct hal_cmd_hf_client_call_action cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.action = action; + cmd.index = index; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CALL_ACTION, sizeof(cmd), &cmd, + NULL, NULL, NULL); +} + +static bt_status_t query_current_calls(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_CURRENT_CALLS, 0, NULL, + NULL, NULL, NULL); +} + +static bt_status_t query_operator_name(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_OPERATOR_NAME, 0, NULL, + NULL, NULL, NULL); +} + +static bt_status_t retrieve_subsr_info(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_RETRIEVE_SUBSCR_INFO, 0, NULL, + NULL, NULL, NULL); +} + +static bt_status_t send_dtmf(char tone) +{ + struct hal_cmd_hf_client_send_dtmf cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.tone = tone; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_SEND_DTMF, sizeof(cmd), &cmd, + NULL, NULL, NULL); +} + +static bt_status_t request_last_voice_tag_number(void) +{ + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_GET_LAST_VOICE_TAG_NUM, + 0, NULL, NULL, NULL, NULL); +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_HANDSFREE_CLIENT; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_HANDSFREE_CLIENT); + + cbs = NULL; +} + +static bthf_client_interface_t iface = { + .size = sizeof(iface), + .init = init, + .connect = hf_client_connect, + .disconnect = disconnect, + .connect_audio = connect_audio, + .disconnect_audio = disconnect_audio, + .start_voice_recognition = start_voice_recognition, + .stop_voice_recognition = stop_voice_recognition, + .volume_control = volume_control, + .dial = dial, + .dial_memory = dial_memory, + .handle_call_action = call_action, + .query_current_calls = query_current_calls, + .query_current_operator_name = query_operator_name, + .retrieve_subscriber_info = retrieve_subsr_info, + .send_dtmf = send_dtmf, + .request_last_voice_tag_number = request_last_voice_tag_number, + .cleanup = cleanup +}; + +bthf_client_interface_t *bt_get_hf_client_interface(void) +{ + return &iface; +}
diff --git a/android/hal-handsfree.c b/android/hal-handsfree.c new file mode 100644 index 0000000..3847901 --- /dev/null +++ b/android/hal-handsfree.c
@@ -0,0 +1,892 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +#include <cutils/properties.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" +#include "hal-utils.h" + +static const bthf_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_conn_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_conn_state *ev = buf; + + if (cbs->connection_state_cb) + cbs->connection_state_cb(ev->state, + (bt_bdaddr_t *) (ev->bdaddr)); +} + +static void handle_audio_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_audio_state *ev = buf; + + if (cbs->audio_state_cb) + cbs->audio_state_cb(ev->state, (bt_bdaddr_t *) (ev->bdaddr)); +} + +static void handle_vr_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_vr_state *ev = buf; + + if (cbs->vr_cmd_cb) +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->vr_cmd_cb(ev->state, (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->vr_cmd_cb(ev->state); +#endif +} + +static void handle_answer(void *buf, uint16_t len, int fd) +{ + if (cbs->answer_call_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_answer *ev = buf; + + cbs->answer_call_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->answer_call_cmd_cb(); +#endif + } +} + +static void handle_hangup(void *buf, uint16_t len, int fd) +{ + if (cbs->hangup_call_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_hangup *ev = buf; + + cbs->hangup_call_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->hangup_call_cmd_cb(); +#endif + } +} + +static void handle_volume(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_volume *ev = buf; + + if (cbs->volume_cmd_cb) +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->volume_cmd_cb(ev->type, ev->volume, + (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->volume_cmd_cb(ev->type, ev->volume); +#endif +} + +static void handle_dial(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_dial *ev = buf; + uint16_t num_len = ev->number_len; + char *number = NULL; + + if (len != sizeof(*ev) + num_len || + (num_len != 0 && ev->number[num_len - 1] != '\0')) { + error("invalid dial event, aborting"); + exit(EXIT_FAILURE); + } + + if (!cbs->dial_call_cmd_cb) + return; + + if (ev->number_len) + number = (char *) ev->number; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->dial_call_cmd_cb(number, (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->dial_call_cmd_cb(number); +#endif +} + +static void handle_dtmf(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_dtmf *ev = buf; + + if (cbs->dtmf_cmd_cb) +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->dtmf_cmd_cb(ev->tone, (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->dtmf_cmd_cb(ev->tone); +#endif +} + +static void handle_nrec(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_nrec *ev = buf; + + if (cbs->nrec_cmd_cb) +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->nrec_cmd_cb(ev->nrec, (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->nrec_cmd_cb(ev->nrec); +#endif +} + +static void handle_wbs(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_wbs *ev = buf; + + if (cbs->wbs_cb) + cbs->wbs_cb(ev->wbs, (bt_bdaddr_t *) (ev->bdaddr)); +#endif +} + +static void handle_chld(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_chld *ev = buf; + + if (cbs->chld_cmd_cb) +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->chld_cmd_cb(ev->chld, (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->chld_cmd_cb(ev->chld); +#endif +} + +static void handle_cnum(void *buf, uint16_t len, int fd) +{ + if (cbs->cnum_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_cnum *ev = buf; + + cbs->cnum_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->cnum_cmd_cb(NULL); +#endif + } +} + +static void handle_cind(void *buf, uint16_t len, int fd) +{ + if (cbs->cind_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_cind *ev = buf; + + cbs->cind_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->cind_cmd_cb(); +#endif + } +} + +static void handle_cops(void *buf, uint16_t len, int fd) +{ + if (cbs->cops_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_cops *ev = buf; + + cbs->cops_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->cops_cmd_cb(); +#endif + } +} + +static void handle_clcc(void *buf, uint16_t len, int fd) +{ + if (cbs->clcc_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_clcc *ev = buf; + + cbs->clcc_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->clcc_cmd_cb(); +#endif + } +} + +static void handle_unknown_at(void *buf, uint16_t len, int fd) +{ + struct hal_ev_handsfree_unknown_at *ev = buf; + + if (len != sizeof(*ev) + ev->len || + (ev->len != 0 && ev->buf[ev->len - 1] != '\0')) { + error("invalid unknown command event, aborting"); + exit(EXIT_FAILURE); + } + + if (cbs->unknown_at_cmd_cb) +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + cbs->unknown_at_cmd_cb((char *) ev->buf, + (bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->unknown_at_cmd_cb((char *) ev->buf); +#endif +} + +static void handle_hsp_key_press(void *buf, uint16_t len, int fd) +{ + if (cbs->key_pressed_cmd_cb) { +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_handsfree_hsp_key_press *ev = buf; + + cbs->key_pressed_cmd_cb((bt_bdaddr_t *) (ev->bdaddr)); +#else + cbs->key_pressed_cmd_cb(); +#endif + } +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_HANDSFREE_CONN_STATE */ + { handle_conn_state, false, + sizeof(struct hal_ev_handsfree_conn_state) }, + /* HAL_EV_HANDSFREE_AUDIO_STATE */ + { handle_audio_state, false, + sizeof(struct hal_ev_handsfree_audio_state) }, + /* HAL_EV_HANDSFREE_VR */ + { handle_vr_state, false, sizeof(struct hal_ev_handsfree_vr_state) }, + /* HAL_EV_HANDSFREE_ANSWER */ + { handle_answer, false, sizeof(struct hal_ev_handsfree_answer) }, + /* HAL_EV_HANDSFREE_HANGUP */ + { handle_hangup, false, sizeof(struct hal_ev_handsfree_hangup) }, + /* HAL_EV_HANDSFREE_VOLUME */ + { handle_volume, false, sizeof(struct hal_ev_handsfree_volume) }, + /* HAL_EV_HANDSFREE_DIAL */ + { handle_dial, true, sizeof(struct hal_ev_handsfree_dial) }, + /* HAL_EV_HANDSFREE_DTMF */ + { handle_dtmf, false, sizeof(struct hal_ev_handsfree_dtmf) }, + /* HAL_EV_HANDSFREE_NREC */ + { handle_nrec, false, sizeof(struct hal_ev_handsfree_nrec) }, + /* HAL_EV_HANDSFREE_CHLD */ + { handle_chld, false, sizeof(struct hal_ev_handsfree_chld) }, + /* HAL_EV_HANDSFREE_CNUM */ + { handle_cnum, false, sizeof(struct hal_ev_handsfree_cnum) }, + /* HAL_EV_HANDSFREE_CIND */ + { handle_cind, false, sizeof(struct hal_ev_handsfree_cind) }, + /* HAL_EV_HANDSFREE_COPS */ + { handle_cops, false, sizeof(struct hal_ev_handsfree_cops) }, + /* HAL_EV_HANDSFREE_CLCC */ + { handle_clcc, false, sizeof(struct hal_ev_handsfree_clcc) }, + /* HAL_EV_HANDSFREE_UNKNOWN_AT */ + { handle_unknown_at, true, sizeof(struct hal_ev_handsfree_unknown_at) }, + /* HAL_EV_HANDSFREE_HSP_KEY_PRESS */ + { handle_hsp_key_press, false, + sizeof(struct hal_ev_handsfree_hsp_key_press) }, + /* HAL_EV_HANDSFREE_WBS */ + { handle_wbs, false, sizeof(struct hal_ev_handsfree_wbs) }, +}; + +static uint8_t get_mode(void) +{ + char value[PROPERTY_VALUE_MAX]; + + if (get_config("handsfree", value, NULL) > 0) { + if (!strcasecmp(value, "hfp")) + return HAL_MODE_HANDSFREE_HFP; + + if (!strcasecmp(value, "hfp_wbs")) + return HAL_MODE_HANDSFREE_HFP_WBS; + } + + return HAL_MODE_HANDSFREE_HSP_ONLY; +} + +static bt_status_t init_real(bthf_callbacks_t *callbacks, int max_hf_clients) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_HANDSFREE, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_HANDSFREE; + cmd.mode = get_mode(); + cmd.max_clients = max_hf_clients; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_HANDSFREE); + } + + return ret; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t init(bthf_callbacks_t *callbacks, int max_hf_clients) +{ + return init_real(callbacks, max_hf_clients); +} +#else +static bt_status_t init(bthf_callbacks_t *callbacks) +{ + return init_real(callbacks, 1); +} +#endif + +static bt_status_t handsfree_connect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_connect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t disconnect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_disconnect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_DISCONNECT, sizeof(cmd), &cmd, + NULL, NULL, NULL); +} + +static bt_status_t connect_audio(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_connect_audio cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CONNECT_AUDIO, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +static bt_status_t disconnect_audio(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_disconnect_audio cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_DISCONNECT_AUDIO, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +static bt_status_t start_voice_recognition_real(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_start_vr cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memset(&cmd, 0, sizeof(cmd)); + + if (bd_addr) + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_START_VR, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t start_voice_recognition(bt_bdaddr_t *bd_addr) +{ + return start_voice_recognition_real(bd_addr); +} +#else +static bt_status_t start_voice_recognition(void) +{ + return start_voice_recognition_real(NULL); +} +#endif + +static bt_status_t stop_voice_recognition_real(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_stop_vr cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memset(&cmd, 0, sizeof(cmd)); + + if (bd_addr) + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, HAL_OP_HANDSFREE_STOP_VR, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t stop_voice_recognition(bt_bdaddr_t *bd_addr) +{ + return stop_voice_recognition_real(bd_addr); +} +#else +static bt_status_t stop_voice_recognition(void) +{ + return stop_voice_recognition_real(NULL); +} +#endif + +static bt_status_t volume_control_real(bthf_volume_type_t type, int volume, + bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_volume_control cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.type = type; + cmd.volume = volume; + + if (bd_addr) + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_VOLUME_CONTROL, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t volume_control(bthf_volume_type_t type, int volume, + bt_bdaddr_t *bd_addr) +{ + return volume_control_real(type, volume, bd_addr); +} +#else +static bt_status_t volume_control(bthf_volume_type_t type, int volume) +{ + return volume_control_real(type, volume, NULL); +} +#endif + +static bt_status_t device_status_notification(bthf_network_state_t state, + bthf_service_type_t type, + int signal, int battery) +{ + struct hal_cmd_handsfree_device_status_notif cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.state = state; + cmd.type = type; + cmd.signal = signal; + cmd.battery = battery; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t cops_response_real(const char *cops, bt_bdaddr_t *bd_addr) +{ + char buf[IPC_MTU]; + struct hal_cmd_handsfree_cops_response *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!cops) + return BT_STATUS_PARM_INVALID; + + memset(cmd, 0, sizeof(*cmd)); + + if (bd_addr) + memcpy(cmd->bdaddr, bd_addr, sizeof(cmd->bdaddr)); + + /* Size of cmd.buf */ + cmd->len = strlen(cops) + 1; + memcpy(cmd->buf, cops, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_COPS_RESPONSE, + len, cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t cops_response(const char *cops, bt_bdaddr_t *bd_addr) +{ + return cops_response_real(cops, bd_addr); +} +#else +static bt_status_t cops_response(const char *cops) +{ + return cops_response_real(cops, NULL); +} +#endif + +static bt_status_t cind_response_real(int svc, int num_active, int num_held, + bthf_call_state_t state, int signal, + int roam, int batt_chg, + bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_cind_response cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memset(&cmd, 0, sizeof(cmd)); + + if (bd_addr) + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + cmd.svc = svc; + cmd.num_active = num_active; + cmd.num_held = num_held; + cmd.state = state; + cmd.signal = signal; + cmd.roam = roam; + cmd.batt_chg = batt_chg; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CIND_RESPONSE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t cind_response(int svc, int num_active, int num_held, + bthf_call_state_t state, int signal, + int roam, int batt_chg, + bt_bdaddr_t *bd_addr) +{ + return cind_response_real(svc, num_active, num_held, state, signal, + roam, batt_chg, bd_addr); +} +#else +static bt_status_t cind_response(int svc, int num_active, int num_held, + bthf_call_state_t state, int signal, + int roam, int batt_chg) +{ + return cind_response_real(svc, num_active, num_held, state, signal, + roam, batt_chg, NULL); +} +#endif + +static bt_status_t formatted_at_response_real(const char *rsp, + bt_bdaddr_t *bd_addr) +{ + char buf[IPC_MTU]; + struct hal_cmd_handsfree_formatted_at_response *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!rsp) + return BT_STATUS_PARM_INVALID; + + memset(cmd, 0, sizeof(*cmd)); + + if (bd_addr) + memcpy(cmd->bdaddr, bd_addr, sizeof(cmd->bdaddr)); + + cmd->len = strlen(rsp) + 1; + memcpy(cmd->buf, rsp, cmd->len); + + len = sizeof(*cmd) + cmd->len; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE, + len, cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t formatted_at_response(const char *rsp, bt_bdaddr_t *bd_addr) +{ + return formatted_at_response_real(rsp, bd_addr); +} +#else +static bt_status_t formatted_at_response(const char *rsp) +{ + return formatted_at_response_real(rsp, NULL); +} +#endif + +static bt_status_t at_response_real(bthf_at_response_t response, int error, + bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_handsfree_at_response cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (bd_addr) + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + memset(&cmd, 0, sizeof(cmd)); + + cmd.response = response; + cmd.error = error; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_AT_RESPONSE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t at_response(bthf_at_response_t response, int error, + bt_bdaddr_t *bd_addr) +{ + return at_response_real(response, error, bd_addr); +} +#else +static bt_status_t at_response(bthf_at_response_t response, int error) +{ + return at_response_real(response, error, NULL); +} +#endif + +static bt_status_t clcc_response_real(int index, bthf_call_direction_t dir, + bthf_call_state_t state, + bthf_call_mode_t mode, + bthf_call_mpty_type_t mpty, + const char *number, + bthf_call_addrtype_t type, + bt_bdaddr_t *bd_addr) +{ + char buf[IPC_MTU]; + struct hal_cmd_handsfree_clcc_response *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memset(cmd, 0, sizeof(*cmd)); + + if (bd_addr) + memcpy(cmd->bdaddr, bd_addr, sizeof(cmd->bdaddr)); + + cmd->index = index; + cmd->dir = dir; + cmd->state = state; + cmd->mode = mode; + cmd->mpty = mpty; + cmd->type = type; + + if (number) { + cmd->number_len = strlen(number) + 1; + memcpy(cmd->number, number, cmd->number_len); + } else { + cmd->number_len = 0; + } + + len = sizeof(*cmd) + cmd->number_len; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CLCC_RESPONSE, + len, cmd, NULL, NULL, NULL); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t clcc_response(int index, bthf_call_direction_t dir, + bthf_call_state_t state, + bthf_call_mode_t mode, + bthf_call_mpty_type_t mpty, + const char *number, + bthf_call_addrtype_t type, + bt_bdaddr_t *bd_addr) +{ + return clcc_response_real(index, dir, state, mode, mpty, number, type, + bd_addr); +} +#else +static bt_status_t clcc_response(int index, bthf_call_direction_t dir, + bthf_call_state_t state, + bthf_call_mode_t mode, + bthf_call_mpty_type_t mpty, + const char *number, + bthf_call_addrtype_t type) +{ + return clcc_response_real(index, dir, state, mode, mpty, number, type, + NULL); +} +#endif + +static bt_status_t phone_state_change(int num_active, int num_held, + bthf_call_state_t state, + const char *number, + bthf_call_addrtype_t type) +{ + char buf[IPC_MTU]; + struct hal_cmd_handsfree_phone_state_change *cmd = (void *) buf; + size_t len; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd->num_active = num_active; + cmd->num_held = num_held; + cmd->state = state; + cmd->type = type; + + if (number) { + cmd->number_len = strlen(number) + 1; + memcpy(cmd->number, number, cmd->number_len); + } else { + cmd->number_len = 0; + } + + len = sizeof(*cmd) + cmd->number_len; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_PHONE_STATE_CHANGE, + len, cmd, NULL, NULL, NULL); +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_HANDSFREE; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_HANDSFREE); + + cbs = NULL; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static bt_status_t configure_wbs(bt_bdaddr_t *bd_addr, bthf_wbs_config_t config) +{ + struct hal_cmd_handsfree_configure_wbs cmd; + + DBG("%u", config); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + cmd.config = config; + + return hal_ipc_cmd(HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CONFIGURE_WBS, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} +#endif + +static bthf_interface_t iface = { + .size = sizeof(iface), + .init = init, + .connect = handsfree_connect, + .disconnect = disconnect, + .connect_audio = connect_audio, + .disconnect_audio = disconnect_audio, + .start_voice_recognition = start_voice_recognition, + .stop_voice_recognition = stop_voice_recognition, + .volume_control = volume_control, + .device_status_notification = device_status_notification, + .cops_response = cops_response, + .cind_response = cind_response, + .formatted_at_response = formatted_at_response, + .at_response = at_response, + .clcc_response = clcc_response, + .phone_state_change = phone_state_change, + .cleanup = cleanup, +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + .configure_wbs = configure_wbs, +#endif +}; + +bthf_interface_t *bt_get_handsfree_interface(void) +{ + return &iface; +}
diff --git a/android/hal-health.c b/android/hal-health.c new file mode 100644 index 0000000..5d5b111 --- /dev/null +++ b/android/hal-health.c
@@ -0,0 +1,297 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" + +static const bthl_callbacks_t *cbacks = NULL; + +static bool interface_ready(void) +{ + return cbacks != NULL; +} + +static void handle_app_registration_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_health_app_reg_state *ev = buf; + + if (cbacks->app_reg_state_cb) + cbacks->app_reg_state_cb(ev->id, ev->state); +} + +static void handle_channel_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_health_channel_state *ev = buf; + int flags; + + if (fd < 0) + goto end; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + error("health: fcntl GETFL error: %s", strerror(errno)); + return; + } + + /* Clean O_NONBLOCK fd flag as Android Java layer expects */ + if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) { + error("health: fcntl SETFL error: %s", strerror(errno)); + return; + } + +end: + if (cbacks->channel_state_cb) + cbacks->channel_state_cb(ev->app_id, (bt_bdaddr_t *) ev->bdaddr, + ev->mdep_index, ev->channel_id, + ev->channel_state, fd); +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_HEALTH_APP_REG_STATE */ + { handle_app_registration_state, false, + sizeof(struct hal_ev_health_app_reg_state) }, + /* HAL_EV_HEALTH_CHANNEL_STATE */ + { handle_channel_state, false, + sizeof(struct hal_ev_health_channel_state) }, +}; + +static bt_status_t register_application(bthl_reg_param_t *reg, int *app_id) +{ + uint8_t buf[IPC_MTU]; + struct hal_cmd_health_reg_app *cmd = (void *) buf; + struct hal_rsp_health_reg_app rsp; + size_t rsp_len = sizeof(rsp); + bt_status_t status; + uint16_t off, len; + int i; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!reg || !app_id || !reg->application_name) + return BT_STATUS_PARM_INVALID; + + *app_id = -1; + memset(buf, 0, IPC_MTU); + + cmd->num_of_mdep = reg->number_of_mdeps; + + off = 0; + cmd->app_name_off = off; + len = strlen(reg->application_name) + 1; + memcpy(cmd->data, reg->application_name, len); + off += len; + + cmd->provider_name_off = off; + if (reg->provider_name) { + len = strlen(reg->provider_name) + 1; + memcpy(cmd->data + off, reg->provider_name, len); + off += len; + } + + cmd->service_name_off = off; + if (reg->srv_name) { + len = strlen(reg->srv_name) + 1; + memcpy(cmd->data + off, reg->srv_name, len); + off += len; + } + + cmd->service_descr_off = off; + if (reg->srv_desp) { + len = strlen(reg->srv_desp) + 1; + memcpy(cmd->data + off, reg->srv_desp, len); + off += len; + } + + cmd->len = off; + status = hal_ipc_cmd(HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_REG_APP, + sizeof(*cmd) + cmd->len, buf, + &rsp_len, &rsp, NULL); + if (status != BT_STATUS_SUCCESS) + return status; + + for (i = 0; i < reg->number_of_mdeps; i++) { + struct hal_cmd_health_mdep *mdep = (void *) buf; + + memset(buf, 0, IPC_MTU); + mdep->app_id = rsp.app_id; + mdep->role = reg->mdep_cfg[i].mdep_role; + mdep->data_type = reg->mdep_cfg[i].data_type; + mdep->channel_type = reg->mdep_cfg[i].channel_type; + + if (reg->mdep_cfg[i].mdep_description) { + mdep->descr_len = + strlen(reg->mdep_cfg[i].mdep_description) + 1; + memcpy(mdep->descr, reg->mdep_cfg[i].mdep_description, + mdep->descr_len); + } + + status = hal_ipc_cmd(HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_MDEP, + sizeof(*mdep) + mdep->descr_len, + buf, NULL, NULL, NULL); + + if (status != BT_STATUS_SUCCESS) + return status; + } + + *app_id = rsp.app_id; + + return status; +} + +static bt_status_t unregister_application(int app_id) +{ + struct hal_cmd_health_unreg_app cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.app_id = app_id; + + return hal_ipc_cmd(HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_UNREG_APP, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t connect_channel(int app_id, bt_bdaddr_t *bd_addr, + int mdep_cfg_index, int *channel_id) +{ + struct hal_cmd_health_connect_channel cmd; + struct hal_rsp_health_connect_channel rsp; + size_t len = sizeof(rsp); + bt_status_t status; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr || !channel_id) + return BT_STATUS_PARM_INVALID; + + *channel_id = -1; + cmd.app_id = app_id; + cmd.mdep_index = mdep_cfg_index; + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + status = hal_ipc_cmd(HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_CONNECT_CHANNEL, + sizeof(cmd), &cmd, &len, &rsp, NULL); + + if (status == BT_STATUS_SUCCESS) + *channel_id = rsp.channel_id; + + return status; +} + +static bt_status_t destroy_channel(int channel_id) +{ + struct hal_cmd_health_destroy_channel cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.channel_id = channel_id; + + return hal_ipc_cmd(HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_DESTROY_CHANNEL, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t init(bthl_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + /* store reference to user callbacks */ + cbacks = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_HEALTH, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_HEALTH; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbacks = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_HEALTH); + } + + return ret; +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_HEALTH; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_HEALTH); + + cbacks = NULL; +} + +static bthl_interface_t health_if = { + .size = sizeof(health_if), + .init = init, + .register_application = register_application, + .unregister_application = unregister_application, + .connect_channel = connect_channel, + .destroy_channel = destroy_channel, + .cleanup = cleanup +}; + +bthl_interface_t *bt_get_health_interface(void) +{ + return &health_if; +}
diff --git a/android/hal-hidhost.c b/android/hal-hidhost.c new file mode 100644 index 0000000..1a60326 --- /dev/null +++ b/android/hal-hidhost.c
@@ -0,0 +1,404 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "hal-ipc.h" + +static const bthh_callbacks_t *cbacks; + +static bool interface_ready(void) +{ + return cbacks != NULL; +} + +static void handle_conn_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hidhost_conn_state *ev = buf; + + if (cbacks->connection_state_cb) + cbacks->connection_state_cb((bt_bdaddr_t *) ev->bdaddr, + ev->state); +} + +static void handle_info(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hidhost_info *ev = buf; + bthh_hid_info_t info; + + info.attr_mask = ev->attr; + info.sub_class = ev->subclass; + info.app_id = ev->app_id; + info.vendor_id = ev->vendor; + info.product_id = ev->product; + info.version = ev->version; + info.ctry_code = ev->country; + info.dl_len = ev->descr_len; + memcpy(info.dsc_list, ev->descr, info.dl_len); + + if (cbacks->hid_info_cb) + cbacks->hid_info_cb((bt_bdaddr_t *) ev->bdaddr, info); +} + +static void handle_proto_mode(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hidhost_proto_mode *ev = buf; + + if (cbacks->protocol_mode_cb) + cbacks->protocol_mode_cb((bt_bdaddr_t *) ev->bdaddr, + ev->status, ev->mode); +} + +static void handle_idle_time(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hidhost_idle_time *ev = buf; + + if (cbacks->idle_time_cb) + cbacks->idle_time_cb((bt_bdaddr_t *) ev->bdaddr, ev->status, + ev->idle_rate); +} + +static void handle_get_report(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hidhost_get_report *ev = buf; + + if (len != sizeof(*ev) + ev->len) { + error("invalid get report event, aborting"); + exit(EXIT_FAILURE); + } + + if (cbacks->get_report_cb) + cbacks->get_report_cb((bt_bdaddr_t *) ev->bdaddr, ev->status, + ev->data, ev->len); +} + +static void handle_virtual_unplug(void *buf, uint16_t len, int fd) +{ + struct hal_ev_hidhost_virtual_unplug *ev = buf; + + if (cbacks->virtual_unplug_cb) + cbacks->virtual_unplug_cb((bt_bdaddr_t *) ev->bdaddr, + ev->status); +} + +static void handle_handshake(void *buf, uint16_t len, int fd) +{ +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + struct hal_ev_hidhost_handshake *ev = buf; + + if (cbacks->handshake_cb) + cbacks->handshake_cb((bt_bdaddr_t *) ev->bdaddr, ev->status); +#endif +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_HIDHOST_CONN_STATE */ + { handle_conn_state, false, sizeof(struct hal_ev_hidhost_conn_state) }, + /* HAL_EV_HIDHOST_INFO */ + { handle_info, false, sizeof(struct hal_ev_hidhost_info) }, + /* HAL_EV_HIDHOST_PROTO_MODE */ + { handle_proto_mode, false, sizeof(struct hal_ev_hidhost_proto_mode) }, + /* HAL_EV_HIDHOST_IDLE_TIME */ + { handle_idle_time, false, sizeof(struct hal_ev_hidhost_idle_time) }, + /* HAL_EV_HIDHOST_GET_REPORT */ + { handle_get_report, true, sizeof(struct hal_ev_hidhost_get_report) }, + /* HAL_EV_HIDHOST_VIRTUAL_UNPLUG */ + { handle_virtual_unplug, false, + sizeof(struct hal_ev_hidhost_virtual_unplug) }, + { handle_handshake, false, sizeof(struct hal_ev_hidhost_handshake) }, +}; + +static bt_status_t hidhost_connect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hidhost_connect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t disconnect(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hidhost_disconnect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_DISCONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t virtual_unplug(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_hidhost_virtual_unplug cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_VIRTUAL_UNPLUG, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t set_info(bt_bdaddr_t *bd_addr, bthh_hid_info_t hid_info) +{ + struct hal_cmd_hidhost_set_info cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + cmd.attr = hid_info.attr_mask; + cmd.subclass = hid_info.sub_class; + cmd.app_id = hid_info.app_id; + cmd.vendor = hid_info.vendor_id; + cmd.product = hid_info.product_id; + cmd.country = hid_info.ctry_code; + cmd.descr_len = hid_info.dl_len; + memcpy(cmd.descr, hid_info.dsc_list, cmd.descr_len); + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_SET_INFO, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t get_protocol(bt_bdaddr_t *bd_addr, + bthh_protocol_mode_t protocol_mode) +{ + struct hal_cmd_hidhost_get_protocol cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + /* type match IPC type */ + cmd.mode = protocol_mode; + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_GET_PROTOCOL, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t set_protocol(bt_bdaddr_t *bd_addr, + bthh_protocol_mode_t protocol_mode) +{ + struct hal_cmd_hidhost_set_protocol cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + /* type match IPC type */ + cmd.mode = protocol_mode; + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_PROTOCOL, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t get_report(bt_bdaddr_t *bd_addr, + bthh_report_type_t report_type, + uint8_t report_id, + int buffer_size) +{ + struct hal_cmd_hidhost_get_report cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + cmd.id = report_id; + cmd.buf_size = buffer_size; + + /* type match IPC type */ + cmd.type = report_type; + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_GET_REPORT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t set_report(bt_bdaddr_t *bd_addr, + bthh_report_type_t report_type, + char *report) +{ + uint8_t buf[IPC_MTU]; + struct hal_cmd_hidhost_set_report *cmd = (void *) buf; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr || !report) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd->bdaddr, bd_addr, sizeof(cmd->bdaddr)); + cmd->len = strlen(report); + memcpy(cmd->data, report, cmd->len); + + /* type match IPC type */ + cmd->type = report_type; + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_SET_REPORT, + sizeof(*cmd) + cmd->len, buf, NULL, NULL, NULL); +} + +static bt_status_t send_data(bt_bdaddr_t *bd_addr, char *data) +{ + uint8_t buf[IPC_MTU]; + struct hal_cmd_hidhost_send_data *cmd = (void *) buf; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + if (!bd_addr || !data) + return BT_STATUS_PARM_INVALID; + + memcpy(cmd->bdaddr, bd_addr, sizeof(cmd->bdaddr)); + cmd->len = strlen(data); + memcpy(cmd->data, data, cmd->len); + + return hal_ipc_cmd(HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_SEND_DATA, + sizeof(*cmd) + cmd->len, buf, NULL, NULL, NULL); +} + +static bt_status_t init(bthh_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + /* store reference to user callbacks */ + cbacks = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_HIDHOST, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_HIDHOST; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbacks = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_HIDHOST); + } + + return ret; +} + +static void cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_HIDHOST; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_HIDHOST); + + cbacks = NULL; +} + +static bthh_interface_t hidhost_if = { + .size = sizeof(hidhost_if), + .init = init, + .connect = hidhost_connect, + .disconnect = disconnect, + .virtual_unplug = virtual_unplug, + .set_info = set_info, + .get_protocol = get_protocol, + .set_protocol = set_protocol, + .get_report = get_report, + .set_report = set_report, + .send_data = send_data, + .cleanup = cleanup +}; + +bthh_interface_t *bt_get_hidhost_interface(void) +{ + return &hidhost_if; +}
diff --git a/android/hal-ipc-api.txt b/android/hal-ipc-api.txt new file mode 100644 index 0000000..e3b7798 --- /dev/null +++ b/android/hal-ipc-api.txt
@@ -0,0 +1,2737 @@ +Android HAL protocol for Bluetooth +================================== + +The Android HAL daemon for Bluetooth functionality implements the Unix socket +server protocol around /run/bluetooth/daemon (tentative location) or Linux +abstract sockets (tentative name). + +The daemon is single threaded and uses a mainloop for scheduling and general +operation. + +The protocol is SOCK_SEQPACKET based and follows a strict PDU specification +with a generic header and initial registration exchange. The communication +is driven from the HAL with command/response exchanges. The daemon will use +notification to signal events. The protocol is single PDU exchanged based, +meaning every command requires a response. Notification does not require +any confirmation. Not handling this PDU exchange leads to a disconnection of +the socket. + +Command/response and notification use separate sockets. First connected socket +is used for command/response, second for notification. All services are +multi-plexed over same pair of sockets. Separation is done to ease +implementation of simple HAL library with dedicated thread for handling +notification. + +This strict protocol requirement is done to match C based callbacks and +callout functions that are running in a thread inside the HAL and might +block. + + .--Android--. .--Android--. + | daemon | | HAL | + | | Command | | + | | <-------------------------- | | + | | | | + | | --------------------------> | | + | | Response | | + | | | | + | | | | + | | Notification | | + | | --------------------------> | | + | | | | + '-----------' '-----------' + +Every packet will follow the basic header to support simple multi-plexing +over the same socket. It will also support a basic control channel with service +id 0. + + 0 8 16 24 31 + +--------------+--------------+--------------+--------------+ + | Service ID | Opcode | Data Length | + +--------------+--------------+-----------------------------+ + | | + +The unique service ID is assigned by this specification for each HAL. + +As general rule of thumb, the opcode for command matches the opcode for a +response. Or the opcode 0x00 for an error is returned. + +Notification opcodes start from 0x81. + +Opcode 0x80 is reserved and shall not be used. + +All command/response opcodes have the least significant bit not set. And all +notifications have the least significant bit set. + +The HAL modules only have the job to map the callback and event functions +to the protocol. They do not need to do anything else. Below is an example +of a sample transaction for the Bluetooth Core HAL and enabling of an +adapter. + + HAL Daemon + ---------------------------------------------------- + + call enable() --> command 0x01 + return enable() <-- response 0x01 + + call adapter_state_changed() <-- notification 0x81 + return adapter_state_changed() + +When the Android hardware framework calls into the Bluetooth Core HAL +and executes the enable() callback, the HAL module sends the enable +command with opcode 0x01 to the daemon. As soon as the daemon responds, +the callback will return with the appropriate result. + +After the daemon switched on the adapter, it will send a notification +with opcode 0x81 to the HAL module. + +The Bluetooth Core HAL and Bluetooth Socket HAL are guaranteed to be +available from the daemon. All other HAL modules are optional. + +When the Bluetooth Core HAL init() function is called, it should open +the socket and register both "bluetooth" and "socket" service modules. It is +required to register "socket" service at the same time since the HAL module +does not have its own init() function. + +It is possible to send Configure command before registering any services to +customize stack. This step is optional. + +When new profiles are initiated, the get_profile_interface() callback +will load the profile and during init() of the profile, it should register the +specific service. + + Bluetooth main thread Daemon + ------------------------------------------------------- + + init() --> open command socket + --> open notification socket + --> register module "bluetooth" + --> register module "socket" + + get_profile_interface() --> return profile struct + --> continue on Handsfree thread + + + Handsfree thread Daemon + -------------------------------------------------------- + + init() --> register module handsfree + + +Error response is common for all services and has fixed structure: + + Opcode 0x00 - Error response + + Response parameters: Status (1 octet) + + Valid status values: 0x01 = Fail + 0x02 = Not ready + 0x03 = No memory + 0x04 = Busy + 0x05 = Done (already completed) + 0x06 = Unsupported + 0x07 = Parameter invalid + 0x08 = Unhandled + 0x09 = Authentication failure + 0x0a = Remote device down + 0x0b = Authentication rejected + + +Core Service (ID 0) +=================== + + Opcode 0x00 - Error response + + Opcode 0x01 - Register module command/response + + Command parameters: Service id (1 octet) + Mode (1 octet) + Max Clients (4 octets) + Response parameters: <none> + + In case a command is sent for an undeclared service ID, it will + be rejected. Also there will be no notifications for undeclared + service ID. + + Valid Mode values: 0x00 = Default Mode + 0xXX = as defined by service + + In case of an error, the error response will be returned. + + Opcode 0x02 - Unregister module command/response + + Command parameters: Service id (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Configuration + + Command parameters: Num options (1 octet) + Option Type # (1 octet) + Option Length # (2 octets) + Option Value # (variable) + + Response parameters: <none> + + Valid configure option types: 0x00 = Vendor + 0x01 = Model + 0x02 = Name + 0x03 = Serial Number + 0x04 = System ID + 0x05 = PnP ID + 0x06 = Firmware Rev + 0x07 = Hardware Rev + + In case of an error, the error response will be returned. + +Bluetooth Core HAL (ID 1) +========================= + +Android HAL name: "bluetooth" (BT_HARDWARE_MODULE_ID) + + Service modes: 0x00 = Enable BR/EDR/LE if supported (default) + 0x01 = Enable BR/EDR only + 0x02 = Enable LE only + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Enable command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disable command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Get Adapter Properties command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x04 - Get Adapter Property command/response + + Command parameters: Property type (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x05 - Set Adapter Property command/response + + Command parameters: Property type (1 octet) + Property length (2 octets) + Property value (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x06 - Get Remote Device Properties command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x07 - Get Remote Device Property command/response + + Command parameters: Remote address (6 octets) + Property type (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x08 - Set Remote Device Property command/response + + Command parameters: Remote address (6 octets) + Property type (1 octet) + Property length (2 octets) + Property value (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x09 - Get Remote Service Record command/response + + Command parameters: Remote address (6 octets) + UUID (16 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0a - Get Remote Services command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0b - Start Discovery command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0c - Cancel Discovery command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0d - Create Bond command/response + + Command parameters: Remote address (6 octets) + Transport (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0e - Remove Bond command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0f - Cancel Bond command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x10 - PIN Reply command/response + + Command parameters: Remote address (6 octets) + Accept (1 octet) + PIN length (1 octet) + PIN code (16 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x11 - SSP Reply command/response + + Command parameters: Remote address (6 octets) + SSP variant (1 octet) + Accept (1 octet) + Passkey (4 octets) + Response parameters: <none> + + Valid SSP variant values: 0x00 = Passkey Confirmation + 0x01 = Passkey Entry + 0x02 = Consent (for Just Works) + 0x03 = Passkey Notification + + In case of an error, the error response will be returned. + + Opcode 0x12 - DUT Mode Configure command/response + + Command parameters: Enable (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x13 - DUT Mode Send command/response + + Command parameters: Opcode (2 octets) + Length (1 octet) + Data (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x14 - LE Test Mode command/response + + Command parameters: Opcode (2 octets) + Length (1 octet) + Data (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Adapter State Changed notification + + Notifications parameters: State (1 octet) + + Valid state values: 0x00 = Off + 0x01 = On + + Opcode 0x82 - Adapter Properties Changed notification + + Notification parameters: Status (1 octet) + Num properties (1 octet) + Type # (1 octet) + Length # (2 octets) + Value # (variable) + ... + + Opcode 0x83 - Remote Device Properties notification + + Notification parameters: Status (1 octet) + Remote address (6 octets) + Num properties (1 octet) + Type # (1 octet) + Length # (2 octets) + Value # (variable) + ... + + Opcode 0x84 - Device Found notification + + Notification parameters: Num properties (1 octet) + Type # (1 octet) + Length # (2 octets) + Value # (variable) + ... + + Opcode 0x85 - Discovery State Changed notification + + Notifications parameters: State (1 octet) + + Opcode 0x86 - PIN Request notification + + Notification parameters: Remote address (6 octets) + Remote name (249 octets) + Class of device (4 octets) + + Opcode 0x87 - SSP Request notification + + Notification parameters: Remote address (6 octets) + Remote name (249 octets) + Class of device (4 octets) + Pairing variant (1 octet) + Passkey (4 octets) + + Opcode 0x88 - Bond State Changed notification + + Notification parameters: Status (1 octet) + Remote address (6 octets) + Bond state (1 octet) + + Valid bond state values: 0x00 = None + 0x01 = Bonding + 0x02 = Bonded + + Opcode 0x89 - ACL State Changed notification + + Notification parameters: Status (1 octet) + Remote address (6 octets) + ACL state (1 octet) + + Opcode 0x8a - DUT Mode Receive notification + + Notification parameters: Opcode (2 octets) + Length (1 octet) + Data (variable) + + Opcode 0x8b - LE Test Mode notification + + Notification parameters: Status (1 octet) + Num packets (2 octets) + + +Bluetooth Socket HAL (ID 2) +=========================== + +Android HAL name:: "socket" (BT_PROFILE_SOCKETS_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Listen command/response + + Command parameters: Socket type (1 octet) + Service name (256 octets) + Service UUID (16 octets) + Channel (4 octets) + Socket flags (1 octet) + Response parameters: File descriptor (inline) + + Valid socket types: 0x01 = RFCOMM + 0x02 = SCO + 0x03 = L2CAP + + Valid socket flags: 0x01 = Encrypt + 0x02 = Auth + + In case of an error, the error response will be returned. + + Opcode 0x02 - Connect command/response + + Command parameters: Remote address (6 octets) + Socket type (1 octet) + Service UUID (16 octets) + Channel (4 octets) + Socket flags (1 octet) + Response parameters: File descriptor (inline) + + Valid socket types: 0x01 = RFCOMM + 0x02 = SCO + 0x03 = L2CAP + + Valid socket flags: 0x01 = Encrypt + 0x02 = Auth + + In case of an error, the error response will be returned. + + +Bluetooth HID Host HAL (ID 3) +============================ + +Android HAL name: "hidhost" (BT_PROFILE_HIDHOST_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Connect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Virtual Unplug command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x04 - Set Info command/response + + Command parameters: Remote address (6 octets) + Attribute mask (2 octets) + Subclass (1 octet) + Application ID (1 octet) + Vendor ID (2 octets) + Product ID (2 octets) + Version (2 octets) + Country code (1 octet) + Descriptor length (2 octet) + Descriptor value (884 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x05 - Get Protocol command/response + + Command parameters: Remote address (6 octets) + Protocol mode (1 octet) + Response parameters: <none> + + Valid protocol modes: 0x00 = Report + 0x01 = Boot + 0xff = Unsupported + + In case of an error, the error response will be returned. + + Opcode 0x06 - Set Protocol command/response + + Command parameters: Remote address (6 octets) + Protocol mode (1 octet) + Response parameters: <none> + + Valid protocol modes: 0x00 = Report + 0x01 = Boot + 0xff = Unsupported + + In case of an error, the error response will be returned. + + Opcode 0x07 - Get Report command/response + + Command parameters: Remote address (6 octets) + Report type (1 octet) + Report ID (1 octet) + Buffer size (2 octet) + Response parameters: <none> + + Valid report types: 0x01 = Input + 0x02 = Output + 0x03 = Feature + + In case of an error, the error response will be returned. + + Opcode 0x08 - Set Report command/response + + Command parameters: Remote address (6 octets) + Report type (1 octet) + Report length (2 octets) + Report data (Report length) + + Response parameters: <none> + + Valid report types: 0x01 = Input + 0x02 = Output + 0x03 = Feature + + In case of an error, the error response will be returned. + + Opcode 0x09 - Send Data command/response + + Command parameters: Remote address (6 octets) + Data length (2 octets) + Data (Data length) + + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Status is common for many notifications and has fixed value range: + + Status values: 0x00 = Ok + 0x01 = Handshake - Device not ready + 0x02 = Handshake - Invalid report ID + 0x03 = Handshake - Transaction not SPT + 0x04 = Handshake - Invalid parameter + 0x05 = Handshake - Generic error + 0x06 = General error + 0x07 = SDP error + 0x08 = Set protocol error + 0x09 = Device database full + 0x0a = Device type not supported + 0x0b = No resources + 0x0c = Authentication failed + 0x0d = HDL + + Opcode 0x81 - Connection State notification + + Notification parameters: Remote address (6 octets) + Connection State (1 octets) + + Valid connection states: 0x00 = Connected + 0x01 = Connecting + 0x02 = Disconnected + 0x03 = Disconnecting + 0x04 = Failed - Mouse from host + 0x05 = Failed - Keyboard from host + 0x06 = Failed - Too many devices + 0x07 = Failed - No HID driver + 0x08 = Failed - generic + 0x09 = Unknown + + Opcode 0x82 - HID Info notification + + Notification parameters: Remote address (6 octets) + Attribute mask (2 octets) + Subclass (1 octet) + Application ID (1 octet) + Vendor ID (2 octets) + Product ID (2 octets) + Version (2 octets) + Country code (1 octet) + Descriptor length (2 octet) + Descriptor value (884 octets) + + Opcode 0x83 - Protocol Mode notification + + Notification parameters: Remote address (6 octets) + Status (1 octet) + Protocol mode (1 octet) + + Valid protocol modes: 0x00 = Report + 0x01 = Boot + 0xff = Unsupported + + Opcode 0x84 - Idle Time notification + + Notification parameters: Remote address (6 octets) + Status (1 octet) + Idle time (2 octets) + + Opcode 0x85 - Get Report notification + + Notification parameters: Remote address (6 octets) + Status (1 octet) + Report length (2 octets) + Report data (variable) + + Opcode 0x86 - Virtual Unplug notification + + Notification parameters: Remote address (6 octets) + Status (1 octet) + + Opcode 0x87 - Handshake notification + + Notification parameters: Remote address (6 octets) + Status (1 octet) + + Only status values from 0x00 to 0x05 are valid as handshake + status. + + +Bluetooth PAN HAL (ID 4) +======================== + +Android HAL name: "pan" (BT_PROFILE_PAN_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Enable command/response + + Command parameters: Local role (1 octet) + Response parameters: <none> + + Valid role values: 0x00 = None + 0x01 = NAP + 0x02 = PANU + + In case of an error, the error response will be returned. + + Opcode 0x02 - Get Local Role command/response + + Command parameters: <none> + Response parameters: Local role (1 octet) + + Valid role values: 0x00 = None + 0x01 = NAP + 0x02 = PANU + + In case of an error, the error response will be returned. + + Opcode 0x03 - Connect command/response + + Command parameters: Remote address (6 octets) + Local role (1 octet) + Remote role (1 octet) + Response parameters: <none> + + Valid role values: 0x01 = NAP + 0x02 = PANU + + In case of an error, the error response will be returned. + + Opcode 0x04 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Control State notification + + Notification parameters: Control state (1 octet) + Status (1 octet) + Local role (1 octet) + Interface name (17 octet) + + Valid control states: 0x00 = Enabled + 0x01 = Disabled + + Valid role values: 0x00 = None + 0x01 = NAP + 0x02 = PANU + + Opcode 0x82 - Connection State notification + + Notification parameters: Connection state (1 octet) + Status (1 octet) + Remote address (6 octets) + Local role (1 octet) + Remote role (1 octet) + + Valid connection states: 0x00 = Connected + 0x01 = Connecting + 0x02 = Disconnected + 0x03 = Disconnecting + + Valid role values: 0x01 = NAP + 0x02 = PANU + + +Bluetooth Handsfree HAL (ID 5) +============================== + +Android HAL name: "handsfree" (BT_PROFILE_HANDSFREE_ID) + + Service modes: 0x00 = Headset Profile only mode (default) + 0x01 = Handsfree Profile (narrowband speech) + 0x02 = Handsfree Profile (narrowband and wideband speech) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Connect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Connect Audio command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x04 - Disconnect Audio command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x05 - Start Voice Recognition command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x06 - Stop Voice Recognition command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x07 - Volume Control command/response + + Command parameters: Volume type (1 octet) + Volume (1 octet) + Remote address (6 octets) + Response parameters: <none> + + Valid volume types: 0x00 = Speaker + 0x01 = Microphone + + In case of an error, the error response will be returned. + + Opcode 0x08 - Device Status Notification command/response + + Command parameters: Network state (1 octet) + Service type (1 octet) + Signal strength (1 octet) + Battery level (1 octet) + Response parameters: <none> + + Valid network states: 0x00 = Not available + 0x01 = Available + + Valid service types: 0x00 = Home network + 0x01 = Roaming network + + In case of an error, the error response will be returned. + + Opcode 0x09 - COPS Response command/response + + Command parameters: COPS command response (string) + Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0a - CIND Response command/response + + Command parameters: Service (1 octet) + Number of active calls (1 octet) + Number of held calls (1 octet) + Call setup state (1 octet) + Signal strength (1 octet) + Roaming indicator (1 octet) + Battery level (1 octet) + Remote address (6 octets) + Response parameters: <none> + + Valid call setup states: 0x00 = Active + 0x01 = Held + 0x02 = Dialing + 0x03 = Alerting + 0x04 = Incoming + 0x05 = Waiting + 0x06 = Idle + + In case of an error, the error response will be returned. + + Opcode 0x0b - Formatted AT Response command/response + + Command parameters: Pre-formatted AT response (string) + Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0c - AT Response command/response + + Command parameters: Response code (1 octet) + Error code (1 octet) + Remote address (6 octets) + Response parameters: <none> + + Valid response codes: 0x00 = ERROR + 0x01 = OK + + In case of an error, the error response will be returned. + + Opcode 0x0d - CLCC Response command/response + + Command parameters: Call index (1 octet) + Call direction (1 octet) + Call state (1 octet) + Call mode (1 octet) + Call multiparty type (1 octet) + Call number type (1 octet) + Call number (string) + Remote address (6 octets) + Response parameters: <none> + + Valid call directions: 0x00 = Outgoing + 0x01 = Incoming + + Valid call states: 0x00 = Active + 0x01 = Held + 0x02 = Dialing + 0x03 = Alerting + 0x04 = Incoming + 0x05 = Waiting + 0x06 = Idle + + Valid call modes: 0x00 = Voice + 0x01 = Data + 0x02 = Fax + + Valid multiparty types: 0x00 = Single call + 0x01 = Multiparty call + + Valid number types: 0x81 = Unknown + 0x91 = International + + In case of an error, the error response will be returned. + + Opcode 0x0e - Phone Status Change command/response + + Command parameters: Number of active calls (1 octet) + Number of held calls (1 octet) + Call setup state (1 octet) + Call number type (1 octet) + Call number (string) + Response parameters: <none> + + Valid call setup states: 0x00 = Active + 0x01 = Held + 0x02 = Dialing + 0x03 = Alerting + 0x04 = Incoming + 0x05 = Waiting + 0x06 = Idle + + Valid number types: 0x81 = Unknown + 0x91 = International + + In case of an error, the error response will be returned. + + Opcode 0x0f - Configure WBS command/response + + Command parameters: Remote address (6 octets) + Config (1 octet) + Response parameters: <none> + + Valid config values: 0x00 = None + 0x01 = No + 0x02 = Yes + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Connection State notification + + Notification parameters: Connection state (1 octet) + Remote address (6 octets) + + Valid connection states: 0x00 = Disconnected + 0x01 = Connecting + 0x02 = Connected + 0x03 = SLC connected + 0x04 = Disconnecting + + Opcode 0x82 - Audio State notification + + Notification parameters: Audio state (1 octet) + Remote address (6 octets) + + Valid audio states: 0x00 = Disconnected + 0x01 = Connecting + 0x02 = Connected + 0x03 = Disconnecting + + Opcode 0x83 - Voice Recognition Command notification + + Notification parameters: Voice recognition state (1 octet) + Remote address (6 octets) + + Valid voice recognition states: 0x00 = Stopped + 0x01 = Started + + Opcode 0x84 - Answer Call Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x85 - Hangup Call Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x86 - Volume Command notification + + Notification parameters: Volume type (1 octet) + Volume (1 octet) + Remote address (6 octets) + + Valid volume types: 0x00 = Speaker + 0x01 = Microphone + + Opcode 0x87 - Dial Call Command notification + + Notification parameters: Remote address (6 octets) + Number (string) + + Opcode 0x88 - DTMF Command notification + + Notification parameters: Tone (1 octet) + Remote address (6 octets) + + Opcode 0x89 - NREC Command notification + + Notification parameters: NREC types (1 octet) + Remote address (6 octets) + + Valid NREC types: 0x00 = Stop + 0x01 = Start + + Opcode 0x8a - CHLD Command notification + + Notification parameters: NREC types (1 octet) + Remote address (6 octets) + + Valid CHLD types: 0x00 = Release and hold + 0x01 = Release active and accept held + 0x02 = Hold active and accept held + 0x03 = Add held call to conference + + Opcode 0x8b - CNUM Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x8c - CIND Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x8d - COPS Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x8e - CLCC Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x8f - Unknown AT Command notification + + Notification parameters: Remote address (6 octets) + AT command (string) + + Opcode 0x90 - Key Pressed Command notification + + Notification parameters: Remote address (6 octets) + + Opcode 0x91 - WBS Command notification + + Notification parameters: WBS types (1 octet) + Remote address (6 octets) + + Valid WBS types: 0x00 = None + 0x01 = No + 0x02 = Yes + +Bluetooth Advanced Audio HAL (ID 6) +Bluetooth Advanced Audio Sink HAL (ID 13) +========================================= + +Android HAL name: "a2dp" (BT_PROFILE_ADVANCED_AUDIO_ID) +Android HAL name: "a2dp_sink" (BT_PROFILE_ADVANCED_AUDIO__SINK_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Connect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Connection State notification + + Notification parameters: Connection state (1 octet) + Remote address (6 octets) + + Valid connection states: 0x00 = Disconnected + 0x01 = Connecting + 0x02 = Connected + 0x03 = Disconnecting + + Opcode 0x82 - Audio State notification + + Notification parameters: Audio state (1 octet) + Remote address (6 octets) + + Valid connection states: 0x00 = Remote suspend + 0x01 = Stopped + 0x02 = Started + + Opcode 0x83 - Audio Configuration notification + + Notification parameters: Remote address (6 octets) + Sample Rate in Hz (4 octets) + Channel Count (1 octet) + + Valid channel count: 0x01 = Mono + 0x02 = Stereo + +Bluetooth Health HAL (ID 7) +=========================== + +Android HAL name: "health" (BT_PROFILE_HEALTH_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Register Application command/response + + Command parameters: Number of MDEP (1 octet) + Application name offset (2 octets) + Provider name offset (2 octets) + Service name offset (2 octets) + Service description offset (2 octets) + Data length (2 octets) + Data (data length) + Response parameters: Application ID (2 octets) + + Strings are null terminated. + In case of an error, the error response will be returned. + + Opcode 0x02 - Register Application MDEP data command/response + + Command parameters: Application ID (2 octets) + MDEP Role (1 octet) + Data type (2 octets) + Channel type (1 octet) + MDEP description length (2 octets) + MDEP description (MDEP desciption length) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Unregister Application command/response + + Command parameters: Application ID (2 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x04 - Connect Channel command/response + + Command parameters: Application ID (2 octets) + Remote address (6 octets) + MDEP index (1 octet) + Response parameters: Channel ID (2 octets) + + In case of an error, the error response will be returned. + + Opcode 0x05 - Destroy Channel command/response + + Command parameters: Channel ID (2 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Application Registration State notification + + Notification parameters: Application ID (2 octets) + Application state (1 octet) + + Valid application states: 0x00 = Registration success + 0x01 = Registration failed + 0x02 = Deregistration success + 0x03 = Deregistration failed + + Opcode 0x82 - Channel State notification + + Notification parameters: Application ID (2 octets) + Remote address (6 octets) + MDEP index (1 octet) + Channel ID (2 octets) + Channel state (1 octet) + File descriptor (inline) + + Valid channel states: 0x00 = Connecting + 0x01 = Connected + 0x02 = Disconnecting + 0x03 = Disconnected + 0x04 = Destroyed + + +Bluetooth Remote Control Target HAL (ID 8) +=================================== + +Android HAL name: "avrcp" (BT_PROFILE_AV_RC_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Get Play Status Response command/response + + Command parameters: Status (1 octet) + Duration (4 octets) + Position (4 octets) + + In case of an error, the error response will be returned. + + Valid status values: 0x00 = Stopped + 0x01 = Playing + 0x02 = Paused + 0x03 = Fwd seek + 0x04 = Rev seek + 0xff = Error + + Opcode 0x02 - List Player Attributes Response command/response + + Command parameters: Number of attributes (1 octet) + Attribute # (1 octet) + ... + + In case of an error, the error response will be returned. + + Valid attributes: 0x01 = Equalizer + 0x02 = Repead + 0x03 = Shuffle + 0x04 = Scan + + Opcode 0x03 - List Player Values Response command/response + + Command parameters: Number of values (1 octet) + Value # (1 octet) + ... + + In case of an error, the error response will be returned. + + Opcode 0x04 - Get Player Values Response command/response + + Command parameters: Number of attributes (1 octet) + Attribute # (1 octet) + Value # (1 octet) + ... + + In case of an error, the error response will be returned. + + Valid attributes: Same as in List Player Attributes + + Opcode 0x05 - Get Player Attributes Text Response command/response + + Command parameters: Number of attributes (1 octet) + Attribute # (1 octet) + Attribute # text length (1 octet) + Attribute # text (variable) + ... + + In case of an error, the error response will be returned. + + Valid attributes: Same as in List Player Attributes + + Opcode 0x06 - Get Player Values Text Response command/response + + Command parameters: Number of values (1 octet) + Value # (1 octet) + Value # text length (1 octet) + Value # text (variable) + ... + + In case of an error, the error response will be returned. + + Opcode 0x07 - Get Element Attributes Text Response command/response + + Command parameters: Number of elements (1 octet) + Element # (1 octet) + Element # text length (1 octet) + Element # text (variable) + ... + + In case of an error, the error response will be returned. + + Valid elements: 0x01 = Title + 0x02 = Artist + 0x03 = Album + 0x04 = Track Number + 0x05 = Number of Tracks + 0x06 = Genre + 0x06 = Duration + + Opcode 0x08 - Set Player Attributes Value Response command/response + + Command parameters: Status (1 octet) + + In case of an error, the error response will be returned. + + Valid status values: Same as in Get Play Status Response + + Opcode 0x09 - Register Notification Response command/response + + Command parameters: Event (1 octet) + Type (1 octet) + Data length (1 octet) + Data (variable) + + In case of an error, the error response will be returned. + + Valid event values: 0x01 = Status Changed + 0x02 = Track Changed + 0x03 = Track Reached End + 0x04 = Track Reached Start + 0x05 = Position Changed + 0x08 = Setting Changed + + Valid type values : 0x00 = Interim + 0x01 = Changed + + Opcode 0x0a - Set Volume command/response + + Command parameters: Value (1 octet) + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Remote Features notification + + Notification parameters: Remote address (6 octets) + Features (1 octet) + + Valid features values : 0x00 = None + 0x01 = Metadata + 0x02 = Absolute Volume + 0x03 = Browse + + Opcode 0x82 - Get Play Status notification + + Notification parameters: <none> + + Opcode 0x83 - List Player Attributes notification + + Notification parameters: <none> + + Opcode 0x84 - List Player Values notification + + Notification parameters: Attribute (1 octet) + + Valid attribute values: Same as in List Player Attributes + + Opcode 0x85 - Get Player Values notification + + Notification parameters: Number of attributes (1 octet) + Attribute # (1 octet) + ... + + Valid attribute values: Same as in List Player Attributes + + Opcode 0x86 - Get Player Attributes Text notification + + Notification parameters: Number of attributes (1 octet) + Attribute # (1 octet) + ... + + Valid attribute values: Same as in List Player Attributes + + Opcode 0x87 - Get Player Values Text notification + + Notification parameters: Attribute (1 octet) + Number of values (1 octet) + Value # (1 octet) + ... + + Valid attribute values: Same as in List Player Attributes + + Opcode 0x88 - Set Player Values notification + + Notification parameters: Number of attributes (1 octet) + Attribute # (1 octet) + Value # (1 octet) + ... + + Valid attribute values: Same as in List Player Attributes + + Opcode 0x89 - Get Element Attributes notification + + Notification parameters: Number of attributes (1 octet) + Attribute # (1 octet) + ... + + Valid attribute values: Same as in Get Element Attribute + + Opcode 0x8a - Register Notification notification + + Notification parameters: Event (1 octet) + Parameter (4 octets) + + Valid event values: Same as in Register Notification + + Opcode 0x8b - Volume Changed notification + + Notification parameters: Volume (1 octet) + Type (1 octet) + + Valid type values: Same as in Register Notification + + Opcode 0x8c - Passthrough Command notification + + Notification parameters: ID (1 octet) + State (1 octet) + +Bluetooth GATT HAL (ID 9) +========================= + +Android HAL name: "gatt" (BT_PROFILE_GATT_ID) + +Structures: + + GATT Service ID: UUID (16 octets) + Instance ID (1 octet) + Is Primary (1 octet) + + GATT Included Service ID: UUID (16 octets) + Instance ID (1 octet) + Is Primary (1 octet) + + GATT Characteristic ID: UUID (16 octets) + Instance ID (1 octet) + + GATT Descriptor ID: UUID (16 octets) + Instance ID (1 octet) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Client Register command/response + + Command parameters: Service UUID (16 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x02 - Client Unregister command/response + + Command parameters: Client Interface (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Client Scan command/response + + Command parameters: Client Interface (4 octets) + Start (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x04 - Client Connect Device command/response + + Command parameters: Client Interface (4 octets) + Remote address (6 octets) + Is Direct (1 octet) + Transport (4 octets) + Response parameters: <none> + + Valid transport value: 0x00 = Auto + 0x01 = BR/EDR + 0x02 = LE + + In case of an error, the error response will be returned. + + Opcode 0x05 - Client Disconnect Device command/response + + Command parameters: Client Interface (4 octets) + Remote address (6 octets) + Connection ID (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x06 - Client Listen command/response + + Command parameters: Client Interface (4 octets) + Start (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x07 - Client Refresh command/response + + Command parameters: Client Interface (4 octets) + Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x08 - Client Search Service command/response + + Command parameters: Connection ID (4 octets) + Filtered (1 octet) + Filter UUID (16 octets) + Response parameters: <none> + + Filter UUID shall only be present when Filtered is non-zero. + + In case of an error, the error response will be returned. + + Opcode 0x09 - Client Get Included Service command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + Continuation (1 octet) + GATT Included Service ID (18 octets) + ... + Response parameters: <none> + + GATT Included Service ID shall only be present when Continuation + is non-zero. + + In case of an error, the error response will be returned. + + Opcode 0x0a - Client Get Characteristic command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + Continuation (1 octet) + GATT Characteristic ID (17 octets) + ... + Response parameters: <none> + + GATT Characteristic ID shall only be present when Continuation + is non-zero. + + In case of an error, the error response will be returned. + + Opcode 0x0b - Client Get Descriptor command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Continuation (1 octet) + GATT Descriptor ID (17 octets) + ... + Response parameters: <none> + + GATT Descriptor ID shall only be present when Continuation is + non-zero. + + In case of an error, the error response will be returned. + + Opcode 0x0c - Client Read Characteristic command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Authorization (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0d - Client Write Characteristic command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Write Type (4 octets) + Length (4 octets) + Authorization Req. (4 octets) + Value (variable) + Response parameters: <none> + + Valid Write Type: 0x01 = No response + 0x02 = Default + 0x03 = Prepare + 0x04 = Signed + + In case of an error, the error response will be returned. + + Opcode 0x0e - Client Read Descriptor command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + GATT Descriptor ID (17 octets) + Authorization Req. (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x0f - Client Write Descriptor command/response + + Command parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + GATT Descriptor ID (17 octets) + Write Type (4 octets) + Length (4 octets) + Authorization Req. (4 octets) + Value (variable) + Response parameters: <none> + + Valid Write Type: 0x01 = No response + 0x02 = Default + 0x03 = Prepare + 0x04 = Signed + + In case of an error, the error response will be returned. + + Opcode 0x10 - Client Execute Write command/response + + Command parameters: Connection ID (4 octets) + Execute (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x11 - Client Register For Notification command/response + + Command parameters: Client Interface (4 octets) + Remote address (6 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x12 - Client Deregister For Notification command/response + + Command parameters: Client Interface (4 octets) + Remote address (6 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x13 - Client Read Remote RSSI command/response + + Command parameters: Client Interface (4 octets) + Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x14 - Client Get Device Type command/response + + Command parameters: Remote address (6 octets) + Response parameters: Device Type + + Valid Device Type: 0x01 = BREDR + 0x02 = BLE + 0x03 = DUAL + + In case of an error, the error response will be returned. + + Opcode 0x15 - Client Set Advertising data command/response + + Command parameters: Server Interface (4 octets) + Set Scan Resp. (1 octet) + Include Name (1 octet) + Include TX Power (1 octet) + Min. Interval (4 octets) + Max. Interval (4 octets) + Appearance (4 octets) + Manufacturer Len. (2 octets) + Manufacturer Data (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x16 - Client Test Command command/response + + Command parameters: Command (4 octets) + Address (6 octets) + UUID (16 octets) + U1 (2 octets) + U2 (2 octets) + U3 (2 octets) + U4 (2 octets) + U5 (2 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x17 - Server Register command/response + + Command parameters: UUID (16 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x18 - Server Unregister command/response + + Command parameters: Server (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x19 - Server Connect Peripheral command/response + + Command parameters: Server (4 octets) + Remote address (6 octets) + Is Direct (1 octet) + Transport (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x1a - Server Disconnect Peripheral command/response + + Command parameters: Server (4 octets) + Remote address (6 octets) + Connection ID (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x1b - Server Add Service command/response + + Command parameters: Server (4 octets) + GATT Service ID (18 octets) + Number of Handles (4 octet) + Response parameters: <none> + + Valid GATT Service ID: UUID (16 octets) + Instance ID (1 octet) + Is Primary (1 octet) + + In case of an error, the error response will be returned. + + Opcode 0x1c - Server Add Included Service command/response + + Command parameters: Server (4 octets) + Service handle (4 octets) + Included handle (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x1d - Server Add Characteristic command/response + + Command parameters: Server (4 octets) + Service handle (4 octets) + UUID (16 octets) + Properties (4 octets) + Permissions (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x1e - Server Add Descriptor command/response + + Command parameters: Server (4 octets) + Service handle (4 octets) + UUID (16 octets) + Permissions (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x1f - Server Start Service command/response + + Command parameters: Server (4 octets) + Service handle (4 octets) + Transport (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x20 - Server Stop Service command/response + + Command parameters: Server (4 octets) + Service handle (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x21 - Server Delete Service command/response + + Command parameters: Server (4 octets) + Service handle (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x22 - Server Send Indication command/response + + Command parameters: Server (4 octets) + Attribute handle (4 octets) + Connection ID (4 octets) + Length (4 octets) + Confirmation (4 octets) + Value (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x23 - Server Send Response command/response + + Command parameters: Connection ID (4 octets) + Transaction ID (4 octets) + Handle (2 octets) + Offset (2 octets) + Auth Request (1 octect) + Status (4 octets) + GATT Response (4 octets) + Response parameters: <none> + + Valid GATT Response: GATT Value (607 octets) + Handle (2 octets) + + Valid GATT Value: Value (600 octets) + Handle (2 octets) + Offset (2 octets) + Length (2 octets) + Authentication Request (1 octet) + + In case of an error, the error response will be returned. + + Opcode 0x24 - Client Scan Filter Params Setup command/response + + Command parameters: Client Interface (4 octets) + Action (4 octets) + Filter Index (4 octets) + Features (4 octets) + List Type (4 octets) + Filter Type (4 octets) + RSSI High Threshold (4 octets) + RSSI Low Threshold (4 octets) + Delivery Mode (4 octets) + Found Timeout (4 octets) + Lost Timeout (4 octets) + Found Timeout Count (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x25 - Client Scan Filter Add Remove command/response + + Command parameters: Client Interface (4 octets) + Action (4 octets) + Filter Type (4 octets) + Filter Index (4 octets) + Company ID (4 octets) + Company ID Mask (4 octets) + UUID (16 octets) + UUID Mask (16 octets) + Address (6 octets) + Address Type (1 octet) + Data Length (4 octets) + Data (variable) + Mask Length (4 octets) + Mask (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x26 - Client Scan Filter Clear command/response + + Command parameters: Client Interface (4 octets) + Filter Index (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x27 - Client Scan Filter Enable command/response + + Command parameters: Client Interface (4 octets) + Enable (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x28 - Client Configure MTU command/response + + Command parameters: Connection ID (4 octets) + MTU (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x29 - Client Connection Parameter Update command/response + + Command parameters: Address (6 octets) + Min Interval (4 octets) + Max Interval (4 octets) + Latency (4 octets) + Timeoutl (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x2a - Client Set Scan Parameters command/response + + Command parameters: Scan Interval (4 octets) + Scan Window (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x2b - Client Setup Multi Advertising command/response + + Command parameters: Client ID (4 octets) + Min Interval (4 octets) + Max Interval (4 octets) + ADV Type (4 octets) + Channel Map (4 octets) + TX Power (4 octets) + Timeout (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x2c - Client Update Multi Advertising command/response + + Command parameters: Client ID (4 octets) + Min Interval (4 octets) + Max Interval (4 octets) + ADV Type (4 octets) + Channel Map (4 octets) + TX Power (4 octets) + Timeout (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x2d - Client Setup Multi Advertising Instance command/response + + Command parameters: Client ID (4 octets) + Set Scan Response (1 octet) + Include Name (1 octet) + Include TX Power (1 octet) + Appearance (4 octets) + Manufacturer Data Length (4 octets) + Manufacturer Data (variable) + Service Data Length (4 octets) + Service Data (variable) + Service UUID Length (4 octets) + Service UUID (variable) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x2e - Client Disable Multi Advertising Instance command/response + + Command parameters: Client ID (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x2f - Client Configure Batchscan command/response + + Command parameters: Client ID (4 octets) + Full Max (4 octets) + Trunc Max (4 octets) + Notify Threshold (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x30 - Client Enable Batchscan command/response + + Command parameters: Client ID (4 octets) + Scan Mode (4 octets) + Scan Interval (4 octets) + Scan Window (4 octets) + Address Type (4 octets) + Discard Rule (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x31 - Client Disable Batchscan command/response + + Command parameters: Client ID (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x32 - Client Read Batchscan Resports command/response + + Command parameters: Client ID (4 octets) + Scan Mode (4 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Client Register notification + + Notification parameters: Status (4 octets) + Client Interface (4 octets) + UUID (16 octets) + + Opcode 0x82 - Client Scan Result notification + + Notification parameters: Address (6 octets) + RSSI (4 octets) + Length (2 octets) + Data (variable) + + Opcode 0x83 - Client Connect Device notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + Client Interface (4 octets) + Address (6 octets) + + Opcode 0x84 - Client Disconnect Device notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + Client Interface (4 octets) + Address (6 octets) + + Opcode 0x85 - Client Search Complete notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + + Opcode 0x86 - Client Search Result notification + + Notification parameters: Connection ID (4 octets) + GATT Service ID (18 octets) + + Opcode 0x87 - Client Get Characteristic notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Char Prop. (4 octets) + + Opcode 0x88 - Client Get Descriptor notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + GATT Descriptor ID (17 octets) + + Opcode 0x89 - Client Get Included Service notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Service ID (18 octets) + GATT Included Service ID (18 octets) + + Opcode 0x8a - Client Register For Notification notification + + Notification parameters: Connection ID (4 octets) + Registered (4 octets) + Status (4 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + + Opcode 0x8b - Client Notify notification + + Notification parameters: Connection ID (4 octets) + Address (6 octets) + GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + Is Notify (1 octet) + Length (2 octets) + Value (variable) + + Opcode 0x8c - Client Read Characteristic notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Read Parameters (variable) + + Valid GATT Read Parameters: GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + GATT Descriptor ID (17 octets) + Value Type (4 octets) + Status (1 octet) + Length (2 octets) + Value (variable) + + Opcode 0x8d - Client Write Characteristic notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Write Parameters (53 octets) + + Valid GATT Write Parameters: GATT Service ID (18 octets) + GATT Characteristic ID (17 octets) + GATT Description ID (17 octets) + Status (1 octet) + + Opcode 0x8e - Client Read Descriptor notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Read Parameters (variable) + + Valid GATT Read Parameters: As described in Read Characteristic + + Opcode 0x8f - Client Write Descriptor notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + GATT Write Parameters (53 octets) + + Valid GATT Write Parameters: As described in Write Characteristic + + Opcode 0x90 - Client Execute Write notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + + Opcode 0x91 - Client Read Remote RSSI notification + + Notification parameters: Client (4 octets) + Address (6 octets) + RSSI (4 octets) + Status (4 octets) + + Opcode 0x92 - Client Listen notification + + Notification parameters: Status (4 octets) + Server Interface (4 octets) + + Opcode 0x93 - Server Register notification + + Notification parameters: Status (4 octets) + Server (4 octets) + UUID (16 octets) + + Opcode 0x94 - Server Connection notification + + Notification parameters: Connection ID (4 octets) + Server (4 octets) + Connected (4 octets) + Address (6 octets) + + Opcode 0x95 - Server Service Added notification + + Notification parameters: Status (4 octets) + Server (4 octets) + GATT Service ID (18 octets) + Service Handle (4 octets) + + Opcode 0x96 - Server Included Service Added notification + + Notification patemeters: Status (4 octets) + Server (4 octets) + Service Handle (4 octets) + Included Service Handle (4 octets) + + Opcode 0x97 - Server Characteristic Added notification + + Notification parameters: Status (4 octets) + Server (4 octets) + UUID (16 octets) + Service Handle (4 octets) + Characteristic Handle (4 octets) + + Opcode 0x98 - Server Descriptor Added notification + + Notification parameters: Status (4 octets) + Server (4 octets) + UUID (6 octets) + Service Handle (4 octets) + Descriptor Handle (4 octets) + + Opcode 0x99 - Server Service Started notification + + Notification parameters: Status (4 octets) + Server (4 octets) + Service Handle (4 octets) + + Opcode 0x9a - Server Service Stopped notification + + Notification parameters: Status (4 octets) + Server (4 octets) + Service Handle (4 octets) + + Opcode 0x9b - Server Service Deleted notification + + Notification parameters: Status (4 octets) + Server (4 octets) + Service Handle (4 octets) + + Opcode 0x9c - Server Request Read notification + + Notification parameters: Connection ID (4 octets) + Trans ID (4 octets) + Address (6 octets) + Attribute Handle (4 octets) + Offset (4 octets) + Is Long (1 octet) + + Opcode 0x9d - Server Request Write notification + + Notification parameters: Connection ID (4 octets) + Trans ID (4 octets) + Address (6 octets) + Attribute Handle (4 octets) + Offset (4 octets) + Length (4 octets) + Need Response (4 octets) + Is Prepare (1 octet) + Value (variable) + + Opcode 0x9e - Server Request Execute Write notification + + Notification parameters: Connection ID (4 octets) + Trans ID (4 octets) + Address (6 octets) + Execute Write (4 octets) + + Opcode 0x9f - Server Response Confirmation notification + + Notification parameters: Status (4 octets) + Handle (4 octets) + + Opcode 0xa0 - Client Configure MTU notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + MTU (4 octets) + + Opcode 0xa1 - Client Filter Configuration notification + + Notification parameters: Action (4 octets) + Client ID (4 octets) + Status (4 octets) + Filter Type (4 octets) + Available Space (4 octets) + + Opcode 0xa2 - Client Filter Parameters notification + + Notification parameters: Action (4 octets) + Client ID (4 octets) + Status (4 octets) + Available Space (4 octets) + + Opcode 0xa3 - Client Filter Status notification + + Notification parameters: Enable (4 octets) + Client ID (4 octets) + Status (4 octets) + + Opcode 0xa4 - Client Multi Advertising Enable notification + + Notification parameters: Client ID (4 octets) + Status (4 octets) + + Opcode 0xa5 - Client Multi Advertising Update notification + + Notification parameters: Client ID (4 octets) + Status (4 octets) + + Opcode 0xa6 - Client Multi Advertising Data notification + + Notification parameters: Client ID (4 octets) + Status (4 octets) + + Opcode 0xa7 - Client Multi Advertising Disable notification + + Notification parameters: Client ID (4 octets) + Status (4 octets) + + Opcode 0xa8 - Client Congestion notification + + Notification parameters: Connection ID (4 octets) + Congested (1 octet) + + Opcode 0xa9 - Client Configure Batchscan notification + + Notification parameters: Client ID (4 octets) + Status (4 octets) + + Opcode 0xaa - Client Enable Batchscan notification + + Notification parameters: Action (4 octets) + Client ID (4 octets) + Status (4 octets) + + Opcode 0xab - Client Batchscan Reports notification + + Notification parameters: Client ID (4 octets) + Status (4 octets) + Report Format (4 octets) + Num Reports (4 octets) + Data Length (4 octets) + Data (variable) + + Opcode 0xac - Client Batchscan Threshold notification + + Notification parameters: Client ID (4 octets) + + Opcode 0xad - Client Track ADV notification + + Notification parameters: Client ID (4 octets) + Filter Index (4 octets) + Address Type (4 octets) + Address (6 octets) + State (4 octets) + + Opcode 0xae - Server Indication Sent notification + + Notification parameters: Connection ID (4 octets) + Status (4 octets) + + Opcode 0xaf - Server Congestion notification + + Notification parameters: Connection ID (4 octets) + Congested (1 octet) + + Opcode 0xb0 - Server MTU Changed notification + + Notification parameters: Connection ID (4 octets) + MTU (4 octets) + + +Bluetooth Handsfree Client HAL (ID 10) +====================================== + +Android HAL name: "hf_client" (BT_PROFILE_HANDSFREE_CLIENT_ID) + +Commands and response: + + Opcode 0x00 - Error response + + Opcode 0x01 - Connect command/respose + + Command parameters: Remote address (6 octects) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disonnect command/response + + Command parameters: Remote address (6 octetcs) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x03 - Connect Audio command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x04 - Disconnect Audio command/response + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x05 - Start Voice Recognition command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x06 - Stop Voice Recognition command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x07 - Volume Control command/response + + Command parameters: Volume type (1 octet) + Volume (1 octet) + Response parameters: <none> + + Valid volume types: 0x00 = Speaker + 0x01 = Microphone + + In case of an error, the error response will be returned. + + Opcode 0x08 - Dial command/response + + Command parameters: Number (string) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x09 - Dial Memory command/response + + Command parameters: Location (4 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x10 - Handle Call Action command/response + + Command parameters: Action (1 octet) + Call Index (1 octet) + Response parameters: <none> + + Valid actions: 0x00 = CHLD_0 + 0x01 = CHLD_1 + 0x02 = CHLD_2 + 0x03 = CHLD_3 + 0x04 = CHLD_4 + 0x05 = CHLD_1x + 0x06 = CHLD_2x + 0x07 = ATA + 0x08 = CHUP + 0x09 = BTRH_0 + 0x10 = BTRH_1 + 0x11 = BTRH_2 + + In case of an error, the error response will be returned. + + Opcode 0x11 - Query Current Calls commad/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x12 - Query Current Operator Name + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x13 - Retrieve Subscriber Info command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x14 - Send DTMF Tone command/response + + Command parameters: Tone (1 octet) + Response parameters: <none> + + In case of an error, the error response will be returned. + + Opcode 0x15 - Request Last Voice Tag Number command/response + + Command parameters: <none> + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Connection State Changed notification + + Notification parameters: State (1 octet) + Peer Features (4 octets) + CHLD Features (4 octets) + Address (6 octets) + + Valid State values: 0x00 = Disconnected + 0x01 = Connecting + 0x02 = Connected + 0x03 = SLC Connected + 0x04 = Disconnecting + + Peer Features is a bitmask of the supported features. Currently + available bits: + + 0 Three way calling + 1 Echo cancellation and/or noise reduction + 2 Voice recognition + 3 In band ring tone + 4 Attach a number to a voice tag + 5 Ability to reject a call + 6 Enhanced call status + 7 Enhanced call control + 8 Extended Error Result Codes + 9 Codec negotiations + 10-31 Reserved for future use + + CHLD Features is a bitmask of the supported features. Currently + available bits: + + 0 Release waiting call or held calls + 1 Release active calls and accept other call + 2 Release specified active call only + 3 Place all active calls on hold and accept other call + 4 Request private mode with secified call + 5 Add a held call to the multiparty + 6 Connect two calls and leave multiparty + 7-31 Reserved for future use + + Note: Peer and CHLD Features are valid only in SCL Connected state + + + Opcode 0x82 - Audio State Changed notification + + Notification parameters: State (1 octet) + Address (6 octets) + + Valid State values: 0x00 = Disconnected + 0x01 = Connecting + 0x02 = Connected + 0x03 = Connected mSBC + + Opcode 0x83 - Voice Recognition State Changed notification + + Notification parameters: State (1 octet) + + Valid State values: 0x00 = VR Stopped + 0x01 = VR Started + + Opcode 0x84 - Network State Changed notification + + Notification parameters: State (1 octet) + + Valid State values: 0x00 = Network Not Available + 0x01 = Network Available + + Opcode 0x85 - Network Roaming Type Changed notification + + Notification parameters: Type (1 octet) + + Valid Type values: 0x00 = Home + 0x01 = Roaming + + Opcode 0x86 - Network Signal Strength notification + + Notification parameters: Signal Strength (1 octet) + + Opcode 0x87 - Battery Level notification + + Notification parameters: Battery Level (1 octet) + + Opcode 0x88 - Current Operator Name notification + + Notification parameters: Name (string) + + Opcode 0x89 - Call Indicatior notification + + Notification parameters: Call (1 octet) + + Valid Call values: 0x00 = No Call In Progress + 0x01 = Call In Progress + + Opcode 0x8a - Call Setup Indicator notification + + Notification parameters: Call Setup (1 octet) + + Valid Call Setup values: 0x00 = None + 0x01 = Incoming + 0x02 = Outgoing + 0x03 = Alerting + + Opcode 0x8b - Call Held Indicator notification + + Notification parameters: Call Held (1 octet) + + Valid Call Held values: 0x00 = None + 0x01 = Hold and Active + 0x02 = Hold + + Opcode 0x8c - Resposne and Hold Status notification + + Notification parameters: Status (1 octet) + + Valid Status values: 0x00 = Held + 0x01 = Accept + 0x02 = Reject + + Opcode 0x8d - Calling Line Identification notification + + Notification parameters: Number (string) + + Note: This will be called only on incoming call if number is + provided. + + Opcode 0x8e - Call Waiting notification + + Notification parameters: Nunmber (string) + + Opcode 0x8f - Current Calls List notification + + Notification parameters: Index (1 octet) + Direction (1 octet) + Call State (1 octet) + Multiparty (1 octet) + Number (string) + + Valid Direction values: 0x00 = Outgoing + 0x01 = Incoming + + Valid Call Sate values: 0x00 = Active + 0x01 = Held + 0x02 = Dialing + 0x03 = Alerting + 0x04 = Incoming + 0x05 = Waiting + 0x06 = Call held by Response and Hold + + Valid Multiparty values: 0x00 = Single Call + 0x01 = Multiparty (conference) Call + + Note: Number might be empty + + Opcode 0x90 - Volume Changed notification + + Notification parameters: Type (1 octet) + Volume (1 octet) + + Valid Type values: 0x00 = Speaker + 0x01 = Microphone + + Opcode 0x91 - Command Complete Callback notification + + Notification parameters: Type (1 octet) + CME (1 octet) + + Valid Type values: 0x00 = OK + 0x01 = Error + 0x02 = Error no carrier + 0x03 = Error busy + 0x04 = Error no answer + 0x05 = Error delayed + 0x06 = Error blacklisted + 0x07 = Error CME + + Note: CME parameter is valid only for Error CME type + + Opcode 0x92 - Subscriber Service Info Callback notification + + Notification parameters: Name (string) + Type (1 octet) + + Valid Type values: 0x00 = Service unknown + 0x01 = Service voice + 0x02 = Service fax + + Opcode 0x93 - In Band Ring Settings Callback notification + + Notification parameters: State (1 octet) + + Valid State values: 0x00 = In band ringtone not provided + 0x01 = In band ringtone provided + + Opcode 0x94 - Last Voice Call Tag Number Callback notification + + Notification parameters: Number (string) + + Opcode 0x95 - Ring Indication notification + + Notification parameters: <none> + + +Bluetooth Map Client HAL (ID 11) +========================= + +Android HAL name: "map_client" (BT_PROFILE_MAP_CLIENT_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Get Remote MAS Instances + + Command parameters: Remote address (6 octets) + Response parameters: <none> + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Remote MAS Instances notification + + Notification parameters: Status (1 octet) + Remote address (6 octets) + Number of instances (4 octets) + Instance ID # (4 octets) + Channel # (4 octets) + Message types (4 octets) + Name # (string) + +Bluetooth Remote Control Controller HAL (ID 12) +=================================== + +Android HAL name: "avrcp-ctrl" (BT_PROFILE_AV_RC_CTRL_ID) + +Commands and responses: + + Opcode 0x00 - Error response + + Opcode 0x01 - Send Pass Through command/response + + Command parameters: Remote Address (6 octets) + Key Code (1 octet) + Key State (1 octet) + + In case of an error, the error response will be returned. + +Notifications: + + Opcode 0x81 - Passthrough Response Notification + + Notification parameters: ID (1 octet) + Key State (1 octet) + + Opcode 0x82 - Connection State Notification + + Notification parameters: State (1 octet) + Remote Address (6 octets)
diff --git a/android/hal-ipc.c b/android/hal-ipc.c new file mode 100644 index 0000000..363072c --- /dev/null +++ b/android/hal-ipc.c
@@ -0,0 +1,471 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <pthread.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdbool.h> +#include <poll.h> +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> + +#include <cutils/properties.h> + +#include "hal.h" +#include "hal-msg.h" +#include "hal-log.h" +#include "ipc-common.h" +#include "hal-ipc.h" + +#define CONNECT_TIMEOUT (10 * 1000) + +static int listen_sk = -1; +static int cmd_sk = -1; +static int notif_sk = -1; + +static pthread_mutex_t cmd_sk_mutex = PTHREAD_MUTEX_INITIALIZER; + +static pthread_t notif_th = 0; + +struct service_handler { + const struct hal_ipc_handler *handler; + uint8_t size; +}; + +static struct service_handler services[HAL_SERVICE_ID_MAX + 1]; + +void hal_ipc_register(uint8_t service, const struct hal_ipc_handler *handlers, + uint8_t size) +{ + services[service].handler = handlers; + services[service].size = size; +} + +void hal_ipc_unregister(uint8_t service) +{ + services[service].handler = NULL; + services[service].size = 0; +} + +static bool handle_msg(void *buf, ssize_t len, int fd) +{ + struct ipc_hdr *msg = buf; + const struct hal_ipc_handler *handler; + uint8_t opcode; + + if (len < (ssize_t) sizeof(*msg)) { + error("IPC: message too small (%zd bytes)", len); + return false; + } + + if (len != (ssize_t) (sizeof(*msg) + msg->len)) { + error("IPC: message malformed (%zd bytes)", len); + return false; + } + + /* if service is valid */ + if (msg->service_id > HAL_SERVICE_ID_MAX) { + error("IPC: unknown service (0x%x)", msg->service_id); + return false; + } + + /* if service is registered */ + if (!services[msg->service_id].handler) { + error("IPC: unregistered service (0x%x)", msg->service_id); + return false; + } + + /* if opcode fit valid range */ + if (msg->opcode < HAL_MINIMUM_EVENT) { + error("IPC: invalid opcode for service 0x%x (0x%x)", + msg->service_id, msg->opcode); + return false; + } + + /* + * opcode is used as table offset and must be adjusted as events start + * with HAL_MINIMUM_EVENT offset + */ + opcode = msg->opcode - HAL_MINIMUM_EVENT; + + /* if opcode is valid */ + if (opcode >= services[msg->service_id].size) { + error("IPC: invalid opcode for service 0x%x (0x%x)", + msg->service_id, msg->opcode); + return false; + } + + handler = &services[msg->service_id].handler[opcode]; + + /* if payload size is valid */ + if ((handler->var_len && handler->data_len > msg->len) || + (!handler->var_len && handler->data_len != msg->len)) { + error("IPC: message size invalid for service 0x%x opcode 0x%x " + "(%u bytes)", + msg->service_id, msg->opcode, msg->len); + return false; + } + + handler->handler(msg->payload, msg->len, fd); + + return true; +} + +static void *notification_handler(void *data) +{ + struct msghdr msg; + struct iovec iv; + struct cmsghdr *cmsg; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + char buf[IPC_MTU]; + ssize_t ret; + int fd; + + bt_thread_associate(); + + while (true) { + memset(&msg, 0, sizeof(msg)); + memset(buf, 0, sizeof(buf)); + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + + iv.iov_base = buf; + iv.iov_len = sizeof(buf); + + msg.msg_iov = &iv; + msg.msg_iovlen = 1; + + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + ret = recvmsg(notif_sk, &msg, 0); + if (ret < 0) { + error("Receiving notifications failed: %s", + strerror(errno)); + goto failed; + } + + /* socket was shutdown */ + if (ret == 0) { + pthread_mutex_lock(&cmd_sk_mutex); + if (cmd_sk == -1) { + pthread_mutex_unlock(&cmd_sk_mutex); + break; + } + pthread_mutex_unlock(&cmd_sk_mutex); + + error("Notification socket closed"); + goto failed; + } + + fd = -1; + + /* Receive auxiliary data in msg */ + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); + break; + } + } + + if (!handle_msg(buf, ret, fd)) + goto failed; + } + + close(notif_sk); + notif_sk = -1; + + bt_thread_disassociate(); + + DBG("exit"); + + return NULL; + +failed: + exit(EXIT_FAILURE); +} + +static int accept_connection(int sk) +{ + int err; + struct pollfd pfd; + int new_sk; + + memset(&pfd, 0 , sizeof(pfd)); + pfd.fd = sk; + pfd.events = POLLIN; + + err = poll(&pfd, 1, CONNECT_TIMEOUT); + if (err < 0) { + err = errno; + error("Failed to poll: %d (%s)", err, strerror(err)); + return -1; + } + + if (err == 0) { + error("bluetoothd connect timeout"); + return -1; + } + + new_sk = accept(sk, NULL, NULL); + if (new_sk < 0) { + err = errno; + error("Failed to accept socket: %d (%s)", err, strerror(err)); + return -1; + } + + return new_sk; +} + +bool hal_ipc_accept(void) +{ + int err; + + cmd_sk = accept_connection(listen_sk); + if (cmd_sk < 0) + return false; + + notif_sk = accept_connection(listen_sk); + if (notif_sk < 0) { + close(cmd_sk); + cmd_sk = -1; + return false; + } + + err = pthread_create(¬if_th, NULL, notification_handler, NULL); + if (err) { + notif_th = 0; + error("Failed to start notification thread: %d (%s)", err, + strerror(err)); + close(cmd_sk); + cmd_sk = -1; + close(notif_sk); + notif_sk = -1; + return false; + } + + info("IPC connected"); + + return true; +} + +bool hal_ipc_init(const char *path, size_t size) +{ + struct sockaddr_un addr; + int sk; + int err; + + sk = socket(AF_LOCAL, SOCK_SEQPACKET, 0); + if (sk < 0) { + err = errno; + error("Failed to create socket: %d (%s)", err, + strerror(err)); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + memcpy(addr.sun_path, path, size); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = errno; + error("Failed to bind socket: %d (%s)", err, strerror(err)); + close(sk); + return false; + } + + if (listen(sk, 2) < 0) { + err = errno; + error("Failed to listen on socket: %d (%s)", err, + strerror(err)); + close(sk); + return false; + } + + listen_sk = sk; + + return true; +} + +void hal_ipc_cleanup(void) +{ + close(listen_sk); + listen_sk = -1; + + pthread_mutex_lock(&cmd_sk_mutex); + if (cmd_sk >= 0) { + close(cmd_sk); + cmd_sk = -1; + } + pthread_mutex_unlock(&cmd_sk_mutex); + + if (notif_sk < 0) + return; + + shutdown(notif_sk, SHUT_RD); + + pthread_join(notif_th, NULL); + notif_th = 0; +} + +int hal_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len, void *param, + size_t *rsp_len, void *rsp, int *fd) +{ + ssize_t ret; + struct msghdr msg; + struct iovec iv[2]; + struct ipc_hdr cmd; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + struct ipc_status s; + size_t s_len = sizeof(s); + + if (cmd_sk < 0) { + error("Invalid cmd socket passed to hal_ipc_cmd"); + goto failed; + } + + if (!rsp || !rsp_len) { + memset(&s, 0, s_len); + rsp_len = &s_len; + rsp = &s; + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmd, 0, sizeof(cmd)); + + cmd.service_id = service_id; + cmd.opcode = opcode; + cmd.len = len; + + iv[0].iov_base = &cmd; + iv[0].iov_len = sizeof(cmd); + + iv[1].iov_base = param; + iv[1].iov_len = len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + pthread_mutex_lock(&cmd_sk_mutex); + + ret = sendmsg(cmd_sk, &msg, 0); + if (ret < 0) { + error("Sending command failed:%s", strerror(errno)); + pthread_mutex_unlock(&cmd_sk_mutex); + goto failed; + } + + /* socket was shutdown */ + if (ret == 0) { + error("Command socket closed"); + pthread_mutex_unlock(&cmd_sk_mutex); + goto failed; + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmd, 0, sizeof(cmd)); + + iv[0].iov_base = &cmd; + iv[0].iov_len = sizeof(cmd); + + iv[1].iov_base = rsp; + iv[1].iov_len = *rsp_len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + if (fd) { + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + } + + ret = recvmsg(cmd_sk, &msg, 0); + + pthread_mutex_unlock(&cmd_sk_mutex); + + if (ret < 0) { + error("Receiving command response failed: %s", strerror(errno)); + goto failed; + } + + + if (ret < (ssize_t) sizeof(cmd)) { + error("Too small response received(%zd bytes)", ret); + goto failed; + } + + if (cmd.service_id != service_id) { + error("Invalid service id (0x%x vs 0x%x)", + cmd.service_id, service_id); + goto failed; + } + + if (ret != (ssize_t) (sizeof(cmd) + cmd.len)) { + error("Malformed response received(%zd bytes)", ret); + goto failed; + } + + if (cmd.opcode != opcode && cmd.opcode != HAL_OP_STATUS) { + error("Invalid opcode received (0x%x vs 0x%x)", + cmd.opcode, opcode); + goto failed; + } + + if (cmd.opcode == HAL_OP_STATUS) { + struct ipc_status *s = rsp; + + if (sizeof(*s) != cmd.len) { + error("Invalid status length"); + goto failed; + } + + if (s->code == HAL_STATUS_SUCCESS) { + error("Invalid success status response"); + goto failed; + } + + return s->code; + } + + /* Receive auxiliary data in msg */ + if (fd) { + struct cmsghdr *cmsg; + + *fd = -1; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); + break; + } + } + } + + *rsp_len = cmd.len; + + return BT_STATUS_SUCCESS; + +failed: + exit(EXIT_FAILURE); +}
diff --git a/android/hal-ipc.h b/android/hal-ipc.h new file mode 100644 index 0000000..08ed7cc --- /dev/null +++ b/android/hal-ipc.h
@@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +struct hal_ipc_handler { + void (*handler) (void *buf, uint16_t len, int fd); + bool var_len; + size_t data_len; +}; + +bool hal_ipc_init(const char *path, size_t size); +bool hal_ipc_accept(void); +void hal_ipc_cleanup(void); + +int hal_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len, void *param, + size_t *rsp_len, void *rsp, int *fd); + +void hal_ipc_register(uint8_t service, const struct hal_ipc_handler *handlers, + uint8_t size); +void hal_ipc_unregister(uint8_t service);
diff --git a/android/hal-log.h b/android/hal-log.h new file mode 100644 index 0000000..63ff61b --- /dev/null +++ b/android/hal-log.h
@@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#define LOG_TAG "BlueZ" + +#ifdef __BIONIC__ +#include <cutils/log.h> +#else +#include <stdio.h> +#define LOG_INFO " I" +#define LOG_WARN " W" +#define LOG_ERROR " E" +#define LOG_DEBUG " D" +#define ALOG(pri, tag, fmt, arg...) fprintf(stderr, tag pri": " fmt"\n", ##arg) +#endif + +#define info(fmt, arg...) ALOG(LOG_INFO, LOG_TAG, fmt, ##arg) +#define warn(fmt, arg...) ALOG(LOG_WARN, LOG_TAG, fmt, ##arg) +#define error(fmt, arg...) ALOG(LOG_ERROR, LOG_TAG, fmt, ##arg) +#define DBG(fmt, arg...) ALOG(LOG_DEBUG, LOG_TAG, "%s:%s() "fmt, __FILE__, \ + __func__, ##arg)
diff --git a/android/hal-map-client.c b/android/hal-map-client.c new file mode 100644 index 0000000..adf04fc --- /dev/null +++ b/android/hal-map-client.c
@@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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. + * + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> + +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "hal-ipc.h" + +static const btmce_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +/* Event Handlers */ + +static void remote_mas_instances_to_hal(btmce_mas_instance_t *send_instance, + struct hal_map_client_mas_instance *instance, + int num_instances, uint16_t len) +{ + void *buf = instance; + char *name; + int i; + + DBG(""); + + for (i = 0; i < num_instances; i++) { + name = (char *) instance->name; + if (sizeof(*instance) + instance->name_len > len || + (instance->name_len != 0 && + name[instance->name_len - 1] != '\0')) { + error("invalid remote mas instance %d, aborting", i); + exit(EXIT_FAILURE); + } + + send_instance[i].id = instance->id; + send_instance[i].msg_types = instance->msg_types; + send_instance[i].scn = instance->scn; + send_instance[i].p_name = name; + + len -= sizeof(*instance) + instance->name_len; + buf += sizeof(*instance) + instance->name_len; + instance = buf; + } + + if (!len) + return; + + error("invalid remote mas instances (%u bytes left), aborting", len); + exit(EXIT_FAILURE); +} + +static void handle_remote_mas_instances(void *buf, uint16_t len, int fd) +{ + struct hal_ev_map_client_remote_mas_instances *ev = buf; + btmce_mas_instance_t instances[ev->num_instances]; + + DBG(""); + + len -= sizeof(*ev); + remote_mas_instances_to_hal(instances, ev->instances, ev->num_instances, + len); + + if (cbs->remote_mas_instances_cb) + cbs->remote_mas_instances_cb(ev->status, + (bt_bdaddr_t *) ev->bdaddr, + ev->num_instances, instances); +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_MCE_REMOTE_MAS_INSTANCES */ + { handle_remote_mas_instances, true, + sizeof(struct hal_ev_map_client_remote_mas_instances) } +}; + +/* API */ + +static bt_status_t get_remote_mas_instances(bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_map_client_get_instances cmd; + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(*bd_addr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_MAP_CLIENT, + HAL_OP_MAP_CLIENT_GET_INSTANCES, sizeof(cmd), + &cmd, NULL, NULL, NULL); +} + +static bt_status_t init(btmce_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + /* + * Interface ready check was removed because there is no cleanup + * function to unregister and clear callbacks. MAP client testers may + * restart bluetooth, unregister this profile and try to reuse it. + * This situation make service unregistered but callbacks are still + * set - interface is ready. On android devices there is no need to + * re-init MAP client profile while bluetooth is loaded. + */ + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_MAP_CLIENT, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_MAP_CLIENT; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, 0, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_MAP_CLIENT); + } + + return ret; +} + +static btmce_interface_t iface = { + .size = sizeof(iface), + .init = init, + .get_remote_mas_instances = get_remote_mas_instances +}; + +btmce_interface_t *bt_get_map_client_interface(void) +{ + return &iface; +}
diff --git a/android/hal-msg.h b/android/hal-msg.h new file mode 100644 index 0000000..ea79fa7 --- /dev/null +++ b/android/hal-msg.h
@@ -0,0 +1,2335 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +static const char BLUEZ_HAL_SK_PATH[] = "\0bluez_hal_socket"; + +#define HAL_MINIMUM_EVENT 0x81 + +#define HAL_SERVICE_ID_CORE 0 +#define HAL_SERVICE_ID_BLUETOOTH 1 +#define HAL_SERVICE_ID_SOCKET 2 +#define HAL_SERVICE_ID_HIDHOST 3 +#define HAL_SERVICE_ID_PAN 4 +#define HAL_SERVICE_ID_HANDSFREE 5 +#define HAL_SERVICE_ID_A2DP 6 +#define HAL_SERVICE_ID_HEALTH 7 +#define HAL_SERVICE_ID_AVRCP 8 +#define HAL_SERVICE_ID_GATT 9 +#define HAL_SERVICE_ID_HANDSFREE_CLIENT 10 +#define HAL_SERVICE_ID_MAP_CLIENT 11 +#define HAL_SERVICE_ID_AVRCP_CTRL 12 +#define HAL_SERVICE_ID_A2DP_SINK 13 + +#define HAL_SERVICE_ID_MAX HAL_SERVICE_ID_A2DP_SINK + +/* Core Service */ + +#define HAL_STATUS_SUCCESS IPC_STATUS_SUCCESS +#define HAL_STATUS_FAILED 0x01 +#define HAL_STATUS_NOT_READY 0x02 +#define HAL_STATUS_NOMEM 0x03 +#define HAL_STATUS_BUSY 0x04 +#define HAL_STATUS_DONE 0x05 +#define HAL_STATUS_UNSUPPORTED 0x06 +#define HAL_STATUS_INVALID 0x07 +#define HAL_STATUS_UNHANDLED 0x08 +#define HAL_STATUS_AUTH_FAILURE 0x09 +#define HAL_STATUS_REMOTE_DEVICE_DOWN 0x0a + +#define HAL_OP_STATUS IPC_OP_STATUS + +#define HAL_MODE_DEFAULT 0x00 +#define HAL_MODE_BREDR 0x01 +#define HAL_MODE_LE 0x02 + +#define HAL_OP_REGISTER_MODULE 0x01 +struct hal_cmd_register_module { + uint8_t service_id; + uint8_t mode; + int32_t max_clients; +} __attribute__((packed)); + +#define HAL_OP_UNREGISTER_MODULE 0x02 +struct hal_cmd_unregister_module { + uint8_t service_id; +} __attribute__((packed)); + +#define HAL_CONFIG_VENDOR 0x00 +#define HAL_CONFIG_MODEL 0x01 +#define HAL_CONFIG_NAME 0x02 +#define HAL_CONFIG_SERIAL_NUMBER 0x03 +#define HAL_CONFIG_SYSTEM_ID 0x04 +#define HAL_CONFIG_PNP_ID 0x05 +#define HAL_CONFIG_FW_REV 0x06 +#define HAL_CONFIG_HW_REV 0x07 + +struct hal_config_prop { + uint8_t type; + uint16_t len; + uint8_t val[0]; +} __attribute__((packed)); + +#define HAL_OP_CONFIGURATION 0x03 +struct hal_cmd_configuration { + uint8_t num; + struct hal_config_prop props[0]; +} __attribute__((packed)); + +/* Bluetooth Core HAL API */ + +#define HAL_OP_ENABLE 0x01 + +#define HAL_OP_DISABLE 0x02 + +#define HAL_OP_GET_ADAPTER_PROPS 0x03 + +#define HAL_OP_GET_ADAPTER_PROP 0x04 +struct hal_cmd_get_adapter_prop { + uint8_t type; +} __attribute__((packed)); + +#define HAL_MAX_NAME_LENGTH 249 + +#define HAL_PROP_ADAPTER_NAME 0x01 +#define HAL_PROP_ADAPTER_ADDR 0x02 +#define HAL_PROP_ADAPTER_UUIDS 0x03 +#define HAL_PROP_ADAPTER_CLASS 0x04 +#define HAL_PROP_ADAPTER_TYPE 0x05 +#define HAL_PROP_ADAPTER_SERVICE_REC 0x06 +#define HAL_PROP_ADAPTER_SCAN_MODE 0x07 +#define HAL_PROP_ADAPTER_BONDED_DEVICES 0x08 +#define HAL_PROP_ADAPTER_DISC_TIMEOUT 0x09 + +#define HAL_PROP_DEVICE_NAME 0x01 +#define HAL_PROP_DEVICE_ADDR 0x02 +#define HAL_PROP_DEVICE_UUIDS 0x03 +#define HAL_PROP_DEVICE_CLASS 0x04 +#define HAL_PROP_DEVICE_TYPE 0x05 +#define HAL_PROP_DEVICE_SERVICE_REC 0x06 +struct hal_prop_device_service_rec { + uint8_t uuid[16]; + uint16_t channel; + uint8_t name_len; + uint8_t name[]; +} __attribute__((packed)); + +#define HAL_PROP_DEVICE_FRIENDLY_NAME 0x0a +#define HAL_PROP_DEVICE_RSSI 0x0b +#define HAL_PROP_DEVICE_VERSION_INFO 0x0c +struct hal_prop_device_info { + uint8_t version; + uint16_t sub_version; + uint16_t manufacturer; +} __attribute__((packed)); + +#define HAL_PROP_ADAPTER_LOCAL_LE_FEAT 0x0d +#define HAL_PROP_DEVICE_TIMESTAMP 0xFF + +#define HAL_ADAPTER_SCAN_MODE_NONE 0x00 +#define HAL_ADAPTER_SCAN_MODE_CONN 0x01 +#define HAL_ADAPTER_SCAN_MODE_CONN_DISC 0x02 + +#define HAL_TYPE_BREDR 0x01 +#define HAL_TYPE_LE 0x02 +#define HAL_TYPE_DUAL 0x03 + +#define HAL_OP_SET_ADAPTER_PROP 0x05 +struct hal_cmd_set_adapter_prop { + uint8_t type; + uint16_t len; + uint8_t val[0]; +} __attribute__((packed)); + +#define HAL_OP_GET_REMOTE_DEVICE_PROPS 0x06 +struct hal_cmd_get_remote_device_props { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_GET_REMOTE_DEVICE_PROP 0x07 +struct hal_cmd_get_remote_device_prop { + uint8_t bdaddr[6]; + uint8_t type; +} __attribute__((packed)); + +#define HAL_OP_SET_REMOTE_DEVICE_PROP 0x08 +struct hal_cmd_set_remote_device_prop { + uint8_t bdaddr[6]; + uint8_t type; + uint16_t len; + uint8_t val[0]; +} __attribute__((packed)); + +#define HAL_OP_GET_REMOTE_SERVICE_REC 0x09 +struct hal_cmd_get_remote_service_rec { + uint8_t bdaddr[6]; + uint8_t uuid[16]; +} __attribute__((packed)); + +#define HAL_OP_GET_REMOTE_SERVICES 0x0a +struct hal_cmd_get_remote_services { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_START_DISCOVERY 0x0b + +#define HAL_OP_CANCEL_DISCOVERY 0x0c + +#define BT_TRANSPORT_UNKNOWN 0x00 +#define BT_TRANSPORT_BR_EDR 0x01 +#define BT_TRANSPORT_LE 0x02 + +#define HAL_OP_CREATE_BOND 0x0d +struct hal_cmd_create_bond { + uint8_t bdaddr[6]; + uint8_t transport; +} __attribute__((packed)); + +#define HAL_OP_REMOVE_BOND 0x0e +struct hal_cmd_remove_bond { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_CANCEL_BOND 0x0f +struct hal_cmd_cancel_bond { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_PIN_REPLY 0x10 +struct hal_cmd_pin_reply { + uint8_t bdaddr[6]; + uint8_t accept; + uint8_t pin_len; + uint8_t pin_code[16]; +} __attribute__((packed)); + +#define HAL_SSP_VARIANT_CONFIRM 0x00 +#define HAL_SSP_VARIANT_ENTRY 0x01 +#define HAL_SSP_VARIANT_CONSENT 0x02 +#define HAL_SSP_VARIANT_NOTIF 0x03 + +#define HAL_OP_SSP_REPLY 0x11 +struct hal_cmd_ssp_reply { + uint8_t bdaddr[6]; + uint8_t ssp_variant; + uint8_t accept; + uint32_t passkey; +} __attribute__((packed)); + +#define HAL_OP_DUT_MODE_CONF 0x12 +struct hal_cmd_dut_mode_conf { + uint8_t enable; +} __attribute__((packed)); + +#define HAL_OP_DUT_MODE_SEND 0x13 +struct hal_cmd_dut_mode_send { + uint16_t opcode; + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_OP_LE_TEST_MODE 0x14 +struct hal_cmd_le_test_mode { + uint16_t opcode; + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_OP_GET_CONNECTION_STATE 0x15 +struct hal_cmd_get_connection_state { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +struct hal_rsp_get_connection_state { + int32_t connection_state; +} __attribute__((packed)); + +#define HAL_OP_READ_ENERGY_INFO 0x16 + +/* Bluetooth Socket HAL api */ + +#define HAL_MODE_SOCKET_DEFAULT HAL_MODE_DEFAULT +#define HAL_MODE_SOCKET_DYNAMIC_MAP 0x01 + +#define HAL_SOCK_RFCOMM 0x01 +#define HAL_SOCK_SCO 0x02 +#define HAL_SOCK_L2CAP 0x03 + +#define HAL_SOCK_FLAG_ENCRYPT 0x01 +#define HAL_SOCK_FLAG_AUTH 0x02 + +#define HAL_OP_SOCKET_LISTEN 0x01 +struct hal_cmd_socket_listen { + uint8_t type; + uint8_t name[256]; + uint8_t uuid[16]; + int32_t channel; + uint8_t flags; +} __attribute__((packed)); + +#define HAL_OP_SOCKET_CONNECT 0x02 +struct hal_cmd_socket_connect { + uint8_t bdaddr[6]; + uint8_t type; + uint8_t uuid[16]; + int32_t channel; + uint8_t flags; +} __attribute__((packed)); + +/* Bluetooth HID Host HAL API */ + +#define HAL_OP_HIDHOST_CONNECT 0x01 +struct hal_cmd_hidhost_connect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HIDHOST_DISCONNECT 0x02 +struct hal_cmd_hidhost_disconnect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HIDHOST_VIRTUAL_UNPLUG 0x03 +struct hal_cmd_hidhost_virtual_unplug { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HIDHOST_SET_INFO 0x04 +struct hal_cmd_hidhost_set_info { + uint8_t bdaddr[6]; + uint8_t attr; + uint8_t subclass; + uint8_t app_id; + uint16_t vendor; + uint16_t product; + uint16_t country; + uint16_t descr_len; + uint8_t descr[0]; +} __attribute__((packed)); + +#define HAL_HIDHOST_REPORT_PROTOCOL 0x00 +#define HAL_HIDHOST_BOOT_PROTOCOL 0x01 +#define HAL_HIDHOST_UNSUPPORTED_PROTOCOL 0xff + +#define HAL_OP_HIDHOST_GET_PROTOCOL 0x05 +struct hal_cmd_hidhost_get_protocol { + uint8_t bdaddr[6]; + uint8_t mode; +} __attribute__((packed)); + +#define HAL_OP_HIDHOST_SET_PROTOCOL 0x06 +struct hal_cmd_hidhost_set_protocol { + uint8_t bdaddr[6]; + uint8_t mode; +} __attribute__((packed)); + +#define HAL_HIDHOST_INPUT_REPORT 0x01 +#define HAL_HIDHOST_OUTPUT_REPORT 0x02 +#define HAL_HIDHOST_FEATURE_REPORT 0x03 + +#define HAL_OP_HIDHOST_GET_REPORT 0x07 +struct hal_cmd_hidhost_get_report { + uint8_t bdaddr[6]; + uint8_t type; + uint8_t id; + uint16_t buf_size; +} __attribute__((packed)); + +#define HAL_OP_HIDHOST_SET_REPORT 0x08 +struct hal_cmd_hidhost_set_report { + uint8_t bdaddr[6]; + uint8_t type; + uint16_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_OP_HIDHOST_SEND_DATA 0x09 +struct hal_cmd_hidhost_send_data { + uint8_t bdaddr[6]; + uint16_t len; + uint8_t data[0]; +} __attribute__((packed)); + +/* a2dp source and sink HAL API */ + +#define HAL_OP_A2DP_CONNECT 0x01 +struct hal_cmd_a2dp_connect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_A2DP_DISCONNECT 0x02 +struct hal_cmd_a2dp_disconnect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +/* PAN HAL API */ + +/* PAN Roles */ +#define HAL_PAN_ROLE_NONE 0x00 +#define HAL_PAN_ROLE_NAP 0x01 +#define HAL_PAN_ROLE_PANU 0x02 + +/* PAN Control states */ +#define HAL_PAN_CTRL_ENABLED 0x00 +#define HAL_PAN_CTRL_DISABLED 0x01 + +/* PAN Connection states */ +#define HAL_PAN_STATE_CONNECTED 0x00 +#define HAL_PAN_STATE_CONNECTING 0x01 +#define HAL_PAN_STATE_DISCONNECTED 0x02 +#define HAL_PAN_STATE_DISCONNECTING 0x03 + +/* PAN status values */ +#define HAL_PAN_STATUS_FAIL 0x01 +#define HAL_PAN_STATUS_NOT_READY 0x02 +#define HAL_PAN_STATUS_NO_MEMORY 0x03 +#define HAL_PAN_STATUS_BUSY 0x04 +#define HAL_PAN_STATUS_DONE 0x05 +#define HAL_PAN_STATUS_UNSUPORTED 0x06 +#define HAL_PAN_STATUS_INVAL 0x07 +#define HAL_PAN_STATUS_UNHANDLED 0x08 +#define HAL_PAN_STATUS_AUTH_FAILED 0x09 +#define HAL_PAN_STATUS_DEVICE_DOWN 0x0A + +#define HAL_OP_PAN_ENABLE 0x01 +struct hal_cmd_pan_enable { + uint8_t local_role; +} __attribute__((packed)); + +#define HAL_OP_PAN_GET_ROLE 0x02 +struct hal_rsp_pan_get_role { + uint8_t local_role; +} __attribute__((packed)); + +#define HAL_OP_PAN_CONNECT 0x03 +struct hal_cmd_pan_connect { + uint8_t bdaddr[6]; + uint8_t local_role; + uint8_t remote_role; +} __attribute__((packed)); + +#define HAL_OP_PAN_DISCONNECT 0x04 +struct hal_cmd_pan_disconnect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HEALTH_MDEP_ROLE_SOURCE 0x00 +#define HAL_HEALTH_MDEP_ROLE_SINK 0x01 + +#define HAL_HEALTH_CHANNEL_TYPE_RELIABLE 0x00 +#define HAL_HEALTH_CHANNEL_TYPE_STREAMING 0x01 +#define HAL_HEALTH_CHANNEL_TYPE_ANY 0x02 + +#define HAL_OP_HEALTH_REG_APP 0x01 +struct hal_cmd_health_reg_app { + uint8_t num_of_mdep; + uint16_t app_name_off; + uint16_t provider_name_off; + uint16_t service_name_off; + uint16_t service_descr_off; + uint16_t len; + uint8_t data[0]; +} __attribute__((packed)); + +struct hal_rsp_health_reg_app { + uint16_t app_id; +} __attribute__((packed)); + +#define HAL_OP_HEALTH_MDEP 0x02 +struct hal_cmd_health_mdep { + uint16_t app_id; + uint8_t role; + uint16_t data_type; + uint8_t channel_type; + uint16_t descr_len; + uint8_t descr[0]; +} __attribute__((packed)); + +#define HAL_OP_HEALTH_UNREG_APP 0x03 +struct hal_cmd_health_unreg_app { + uint16_t app_id; +} __attribute__((packed)); + +#define HAL_OP_HEALTH_CONNECT_CHANNEL 0x04 +struct hal_cmd_health_connect_channel { + uint16_t app_id; + uint8_t bdaddr[6]; + uint8_t mdep_index; +} __attribute__((packed)); + +struct hal_rsp_health_connect_channel { + uint16_t channel_id; +} __attribute__((packed)); + +#define HAL_OP_HEALTH_DESTROY_CHANNEL 0x05 +struct hal_cmd_health_destroy_channel { + uint16_t channel_id; +} __attribute__((packed)); + +/* Handsfree HAL API */ + +#define HAL_MODE_HANDSFREE_HSP_ONLY HAL_MODE_DEFAULT +#define HAL_MODE_HANDSFREE_HFP 0x01 +#define HAL_MODE_HANDSFREE_HFP_WBS 0x02 + +#define HAL_OP_HANDSFREE_CONNECT 0x01 +struct hal_cmd_handsfree_connect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_DISCONNECT 0x02 +struct hal_cmd_handsfree_disconnect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_CONNECT_AUDIO 0x03 +struct hal_cmd_handsfree_connect_audio { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_DISCONNECT_AUDIO 0x04 +struct hal_cmd_handsfree_disconnect_audio { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_START_VR 0x05 +struct hal_cmd_handsfree_start_vr { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_STOP_VR 0x06 +struct hal_cmd_handsfree_stop_vr { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_VOLUME_TYPE_SPEAKER 0x00 +#define HAL_HANDSFREE_VOLUME_TYPE_MIC 0x01 + +#define HAL_OP_HANDSFREE_VOLUME_CONTROL 0x07 +struct hal_cmd_handsfree_volume_control { + uint8_t type; + uint8_t volume; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_NETWORK_STATE_NOT_AVAILABLE 0x00 +#define HAL_HANDSFREE_NETWORK_STATE_AVAILABLE 0x01 + +#define HAL_HANDSFREE_SERVICE_TYPE_HOME 0x00 +#define HAL_HANDSFREE_SERVICE_TYPE_ROAMING 0x01 + +#define HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF 0x08 +struct hal_cmd_handsfree_device_status_notif { + uint8_t state; + uint8_t type; + uint8_t signal; + uint8_t battery; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_COPS_RESPONSE 0x09 +struct hal_cmd_handsfree_cops_response { + uint16_t len; + uint8_t bdaddr[6]; + uint8_t buf[0]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_CALL_STATE_ACTIVE 0x00 +#define HAL_HANDSFREE_CALL_STATE_HELD 0x01 +#define HAL_HANDSFREE_CALL_STATE_DIALING 0x02 +#define HAL_HANDSFREE_CALL_STATE_ALERTING 0x03 +#define HAL_HANDSFREE_CALL_STATE_INCOMING 0x04 +#define HAL_HANDSFREE_CALL_STATE_WAITING 0x05 +#define HAL_HANDSFREE_CALL_STATE_IDLE 0x06 + +#define HAL_OP_HANDSFREE_CIND_RESPONSE 0x0A +struct hal_cmd_handsfree_cind_response { + uint8_t svc; + uint8_t num_active; + uint8_t num_held; + uint8_t state; + uint8_t signal; + uint8_t roam; + uint8_t batt_chg; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE 0x0B +struct hal_cmd_handsfree_formatted_at_response { + uint16_t len; + uint8_t bdaddr[6]; + uint8_t buf[0]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_AT_RESPONSE_ERROR 0x00 +#define HAL_HANDSFREE_AT_RESPONSE_OK 0x01 + +#define HAL_OP_HANDSFREE_AT_RESPONSE 0x0C +struct hal_cmd_handsfree_at_response { + uint8_t response; + uint8_t error; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_CALL_DIRECTION_OUTGOING 0x00 +#define HAL_HANDSFREE_CALL_DIRECTION_INCOMING 0x01 + +#define HAL_HANDSFREE_CALL_TYPE_VOICE 0x00 +#define HAL_HANDSFREE_CALL_TYPE_DATA 0x01 +#define HAL_HANDSFREE_CALL_TYPE_FAX 0x02 + +#define HAL_HANDSFREE_CALL_MPTY_TYPE_SINGLE 0x00 +#define HAL_HANDSFREE_CALL_MPTY_TYPE_MULTI 0x01 + +#define HAL_HANDSFREE_CALL_ADDRTYPE_UNKNOWN 0x81 +#define HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL 0x91 + +#define HAL_OP_HANDSFREE_CLCC_RESPONSE 0x0D +struct hal_cmd_handsfree_clcc_response { + uint8_t index; + uint8_t dir; + uint8_t state; + uint8_t mode; + uint8_t mpty; + uint8_t type; + uint8_t bdaddr[6]; + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_OP_HANDSFREE_PHONE_STATE_CHANGE 0x0E +struct hal_cmd_handsfree_phone_state_change { + uint8_t num_active; + uint8_t num_held; + uint8_t state; + uint8_t type; + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_WBS_NONE 0x00 +#define HAL_HANDSFREE_WBS_NO 0x01 +#define HAL_HANDSFREE_WBS_YES 0x02 + +#define HAL_OP_HANDSFREE_CONFIGURE_WBS 0x0F +struct hal_cmd_handsfree_configure_wbs { + uint8_t bdaddr[6]; + uint8_t config; +} __attribute__((packed)); + +/* AVRCP TARGET HAL API */ + +#define HAL_AVRCP_PLAY_STATUS_STOPPED 0x00 +#define HAL_AVRCP_PLAY_STATUS_PLAYING 0x01 +#define HAL_AVRCP_PLAY_STATUS_PAUSED 0x02 +#define HAL_AVRCP_PLAY_STATUS_FWD_SEEK 0x03 +#define HAL_AVRCP_PLAY_STATUS_REV_SEEK 0x04 +#define HAL_AVRCP_PLAY_STATUS_ERROR 0xff + +#define HAL_OP_AVRCP_GET_PLAY_STATUS 0x01 +struct hal_cmd_avrcp_get_play_status { + uint8_t status; + uint32_t duration; + uint32_t position; +} __attribute__((packed)); + +#define HAL_AVRCP_PLAYER_ATTR_EQUALIZER 0x01 +#define HAL_AVRCP_PLAYER_ATTR_REPEAT 0x02 +#define HAL_AVRCP_PLAYER_ATTR_SHUFFLE 0x03 +#define HAL_AVRCP_PLAYER_ATTR_SCAN 0x04 + +#define HAL_OP_AVRCP_LIST_PLAYER_ATTRS 0x02 +struct hal_cmd_avrcp_list_player_attrs { + uint8_t number; + uint8_t attrs[0]; +} __attribute__((packed)); + +#define HAL_OP_AVRCP_LIST_PLAYER_VALUES 0x03 +struct hal_cmd_avrcp_list_player_values { + uint8_t number; + uint8_t values[0]; +} __attribute__((packed)); + +struct hal_avrcp_player_attr_value { + uint8_t attr; + uint8_t value; +} __attribute__((packed)); + +#define HAL_OP_AVRCP_GET_PLAYER_ATTRS 0x04 +struct hal_cmd_avrcp_get_player_attrs { + uint8_t number; + struct hal_avrcp_player_attr_value attrs[0]; +} __attribute__((packed)); + +struct hal_avrcp_player_setting_text { + uint8_t id; + uint8_t len; + uint8_t text[0]; +} __attribute__((packed)); + +#define HAL_OP_AVRCP_GET_PLAYER_ATTRS_TEXT 0x05 +struct hal_cmd_avrcp_get_player_attrs_text { + uint8_t number; + struct hal_avrcp_player_setting_text attrs[0]; +} __attribute__((packed)); + +#define HAL_OP_AVRCP_GET_PLAYER_VALUES_TEXT 0x06 +struct hal_cmd_avrcp_get_player_values_text { + uint8_t number; + struct hal_avrcp_player_setting_text values[0]; +} __attribute__((packed)); + +#define HAL_AVRCP_MEDIA_ATTR_TITLE 0x01 +#define HAL_AVRCP_MEDIA_ATTR_ARTIST 0x02 +#define HAL_AVRCP_MEDIA_ATTR_ALBUM 0x03 +#define HAL_AVRCP_MEDIA_ATTR_TRACK_NUM 0x04 +#define HAL_AVRCP_MEDIA_ATTR_NUM_TRACKS 0x05 +#define HAL_AVRCP_MEDIA_ATTR_GENRE 0x06 +#define HAL_AVRCP_MEDIA_ATTR_DURATION 0x07 + +#define HAL_OP_AVRCP_GET_ELEMENT_ATTRS_TEXT 0x07 +struct hal_cmd_avrcp_get_element_attrs_text { + uint8_t number; + struct hal_avrcp_player_setting_text values[0]; +} __attribute__((packed)); + +#define HAL_OP_AVRCP_SET_PLAYER_ATTRS_VALUE 0x08 +struct hal_cmd_avrcp_set_player_attrs_value { + uint8_t status; +} __attribute__((packed)); + +#define HAL_AVRCP_EVENT_STATUS_CHANGED 0x01 +#define HAL_AVRCP_EVENT_TRACK_CHANGED 0x02 +#define HAL_AVRCP_EVENT_TRACK_REACHED_END 0x03 +#define HAL_AVRCP_EVENT_TRACK_REACHED_START 0x04 +#define HAL_AVRCP_EVENT_POSITION_CHANGED 0x05 +#define HAL_AVRCP_EVENT_SETTING_CHANGED 0x08 + +#define HAL_AVRCP_EVENT_TYPE_INTERIM 0x00 +#define HAL_AVRCP_EVENT_TYPE_CHANGED 0x01 + +#define HAL_OP_AVRCP_REGISTER_NOTIFICATION 0x09 +struct hal_cmd_avrcp_register_notification { + uint8_t event; + uint8_t type; + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_OP_AVRCP_SET_VOLUME 0x0a +struct hal_cmd_avrcp_set_volume { + uint8_t value; +} __attribute__((packed)); + +/* AVRCP CTRL HAL API */ + +#define HAL_OP_AVRCP_CTRL_SEND_PASSTHROUGH 0x01 +struct hal_cmd_avrcp_ctrl_send_passthrough { + uint8_t bdaddr[6]; + uint8_t key_code; + uint8_t key_state; +} __attribute__((packed)); + +/* GATT HAL API */ + +#define HAL_OP_GATT_CLIENT_REGISTER 0x01 +struct hal_cmd_gatt_client_register { + uint8_t uuid[16]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_UNREGISTER 0x02 +struct hal_cmd_gatt_client_unregister { + int32_t client_if; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SCAN 0x03 +struct hal_cmd_gatt_client_scan { + int32_t client_if; + uint8_t start; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_CONNECT 0x04 +struct hal_cmd_gatt_client_connect { + int32_t client_if; + uint8_t bdaddr[6]; + uint8_t is_direct; + int32_t transport; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_DISCONNECT 0x05 +struct hal_cmd_gatt_client_disconnect { + int32_t client_if; + uint8_t bdaddr[6]; + int32_t conn_id; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_LISTEN 0x06 +struct hal_cmd_gatt_client_listen { + int32_t client_if; + uint8_t start; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_REFRESH 0x07 +struct hal_cmd_gatt_client_refresh { + int32_t client_if; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SEARCH_SERVICE 0x08 +struct hal_cmd_gatt_client_search_service { + int32_t conn_id; + uint8_t filtered; + uint8_t filter_uuid[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_GET_INCLUDED_SERVICE 0x09 +struct hal_gatt_srvc_id { + uint8_t uuid[16]; + uint8_t inst_id; + uint8_t is_primary; +} __attribute__((packed)); + +struct hal_cmd_gatt_client_get_included_service { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + uint8_t continuation; + struct hal_gatt_srvc_id incl_srvc_id[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_GET_CHARACTERISTIC 0x0a +struct hal_gatt_gatt_id { + uint8_t uuid[16]; + uint8_t inst_id; +} __attribute__((packed)); + +struct hal_cmd_gatt_client_get_characteristic { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + uint8_t continuation; + struct hal_gatt_gatt_id char_id[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_GET_DESCRIPTOR 0x0b +struct hal_cmd_gatt_client_get_descriptor { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + uint8_t continuation; + struct hal_gatt_gatt_id descr_id[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_READ_CHARACTERISTIC 0x0c +struct hal_cmd_gatt_client_read_characteristic { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + int32_t auth_req; +} __attribute__((packed)); + +#define GATT_WRITE_TYPE_NO_RESPONSE 0x01 +#define GATT_WRITE_TYPE_DEFAULT 0x02 +#define GATT_WRITE_TYPE_PREPARE 0x03 +#define GATT_WRITE_TYPE_SIGNED 0x04 + +#define HAL_OP_GATT_CLIENT_WRITE_CHARACTERISTIC 0x0d +struct hal_cmd_gatt_client_write_characteristic { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + int32_t write_type; + int32_t len; + int32_t auth_req; + uint8_t value[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_READ_DESCRIPTOR 0x0e +struct hal_cmd_gatt_client_read_descriptor { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + struct hal_gatt_gatt_id descr_id; + int32_t auth_req; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_WRITE_DESCRIPTOR 0x0f +struct hal_cmd_gatt_client_write_descriptor { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + struct hal_gatt_gatt_id descr_id; + int32_t write_type; + int32_t len; + int32_t auth_req; + uint8_t value[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_EXECUTE_WRITE 0x10 +struct hal_cmd_gatt_client_execute_write { + int32_t conn_id; + int32_t execute; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_REGISTER_FOR_NOTIFICATION 0x11 +struct hal_cmd_gatt_client_register_for_notification { + int32_t client_if; + uint8_t bdaddr[6]; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_DEREGISTER_FOR_NOTIFICATION 0x12 +struct hal_cmd_gatt_client_deregister_for_notification { + int32_t client_if; + uint8_t bdaddr[6]; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_READ_REMOTE_RSSI 0x13 +struct hal_cmd_gatt_client_read_remote_rssi { + int32_t client_if; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_GET_DEVICE_TYPE 0x14 +struct hal_cmd_gatt_client_get_device_type { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +struct hal_rsp_gatt_client_get_device_type { + uint8_t type; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SET_ADV_DATA 0x015 +struct hal_cmd_gatt_client_set_adv_data { + int32_t server_if; + uint8_t set_scan_rsp; + uint8_t include_name; + uint8_t include_txpower; + int32_t min_interval; + int32_t max_interval; + int32_t appearance; + uint16_t manufacturer_len; + uint16_t service_data_len; + uint16_t service_uuid_len; + uint8_t data[0]; +} __attribute__((packed)); + +#define GATT_CLIENT_TEST_CMD_ENABLE 0x01 +#define GATT_CLIENT_TEST_CMD_CONNECT 0x02 +#define GATT_CLIENT_TEST_CMD_DISCONNECT 0x03 +#define GATT_CLIENT_TEST_CMD_DISCOVER 0x04 +#define GATT_CLIENT_TEST_CMD_READ 0xe0 +#define GATT_CLIENT_TEST_CMD_WRITE 0xe1 +#define GATT_CLIENT_TEST_CMD_INCREASE_SECURITY 0xe2 +#define GATT_CLIENT_TEST_CMD_PAIRING_CONFIG 0xf0 + +#define HAL_OP_GATT_CLIENT_TEST_COMMAND 0x16 +struct hal_cmd_gatt_client_test_command { + int32_t command; + uint8_t bda1[6]; + uint8_t uuid1[16]; + uint16_t u1; + uint16_t u2; + uint16_t u3; + uint16_t u4; + uint16_t u5; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_REGISTER 0x17 +struct hal_cmd_gatt_server_register { + uint8_t uuid[16]; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_UNREGISTER 0x18 +struct hal_cmd_gatt_server_unregister { + int32_t server_if; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_CONNECT 0x19 +struct hal_cmd_gatt_server_connect { + int32_t server_if; + uint8_t bdaddr[6]; + uint8_t is_direct; + int32_t transport; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_DISCONNECT 0x1a +struct hal_cmd_gatt_server_disconnect { + int32_t server_if; + uint8_t bdaddr[6]; + int32_t conn_id; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_ADD_SERVICE 0x1b +struct hal_cmd_gatt_server_add_service { + int32_t server_if; + struct hal_gatt_srvc_id srvc_id; + int32_t num_handles; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_ADD_INC_SERVICE 0x1c +struct hal_cmd_gatt_server_add_inc_service { + int32_t server_if; + int32_t service_handle; + int32_t included_handle; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_ADD_CHARACTERISTIC 0x1d +struct hal_cmd_gatt_server_add_characteristic { + int32_t server_if; + int32_t service_handle; + uint8_t uuid[16]; + int32_t properties; + int32_t permissions; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_ADD_DESCRIPTOR 0x1e +struct hal_cmd_gatt_server_add_descriptor { + int32_t server_if; + int32_t service_handle; + uint8_t uuid[16]; + int32_t permissions; +} __attribute__((packed)); + +#define GATT_SERVER_TRANSPORT_LE_BIT 0x01 +#define GATT_SERVER_TRANSPORT_BREDR_BIT 0x02 + +#define HAL_OP_GATT_SERVER_START_SERVICE 0x1f +struct hal_cmd_gatt_server_start_service { + int32_t server_if; + int32_t service_handle; + int32_t transport; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_STOP_SERVICE 0x20 +struct hal_cmd_gatt_server_stop_service { + int32_t server_if; + int32_t service_handle; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_DELETE_SERVICE 0x21 +struct hal_cmd_gatt_server_delete_service { + int32_t server_if; + int32_t service_handle; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_SEND_INDICATION 0x22 +struct hal_cmd_gatt_server_send_indication { + int32_t server_if; + int32_t attribute_handle; + int32_t conn_id; + int32_t len; + int32_t confirm; + uint8_t value[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_SERVER_SEND_RESPONSE 0x23 +struct hal_cmd_gatt_server_send_response { + int32_t conn_id; + int32_t trans_id; + uint16_t handle; + uint16_t offset; + uint8_t auth_req; + int32_t status; + uint16_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SCAN_FILTER_SETUP 0x024 +struct hal_cmd_gatt_client_scan_filter_setup { + int32_t client_if; + int32_t action; + int32_t filter_index; + int32_t features; + int32_t list_type; + int32_t filter_type; + int32_t rssi_hi; + int32_t rssi_lo; + int32_t delivery_mode; + int32_t found_timeout; + int32_t lost_timeout; + int32_t found_timeout_cnt; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SCAN_FILTER_ADD_REMOVE 0x025 +struct hal_cmd_gatt_client_scan_filter_add_remove { + int32_t client_if; + int32_t action; + int32_t filter_type; + int32_t filter_index; + int32_t company_id; + int32_t company_id_mask; + uint8_t uuid[16]; + uint8_t uuid_mask[16]; + uint8_t address[6]; + uint8_t address_type; + int32_t data_len; + int32_t mask_len; + uint8_t data_mask[0]; /* common buffer for data and mask */ +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SCAN_FILTER_CLEAR 0x26 +struct hal_cmd_gatt_client_scan_filter_clear { + int32_t client_if; + int32_t index; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SCAN_FILTER_ENABLE 0x27 +struct hal_cmd_gatt_client_scan_filter_enable { + int32_t client_if; + uint8_t enable; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_CONFIGURE_MTU 0x28 +struct hal_cmd_gatt_client_configure_mtu { + int32_t conn_id; + int32_t mtu; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_CONN_PARAM_UPDATE 0x29 +struct hal_cmd_gatt_client_conn_param_update { + uint8_t address[6]; + int32_t min_interval; + int32_t max_interval; + int32_t latency; + int32_t timeout; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SET_SCAN_PARAM 0x2a +struct hal_cmd_gatt_client_set_scan_param { + int32_t interval; + int32_t window; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV 0x2b +struct hal_cmd_gatt_client_setup_multi_adv { + int32_t client_if; + int32_t min_interval; + int32_t max_interval; + int32_t type; + int32_t channel_map; + int32_t tx_power; + int32_t timeout; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_UPDATE_MULTI_ADV 0x2c +struct hal_cmd_gatt_client_update_multi_adv { + int32_t client_if; + int32_t min_interval; + int32_t max_interval; + int32_t type; + int32_t channel_map; + int32_t tx_power; + int32_t timeout; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV_INST 0x2d +struct hal_cmd_gatt_client_setup_multi_adv_inst { + int32_t client_if; + uint8_t set_scan_rsp; + uint8_t include_name; + uint8_t include_tx_power; + int32_t appearance; + int32_t manufacturer_data_len; + int32_t service_data_len; + int32_t service_uuid_len; + uint8_t data_service_uuid[0]; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_DISABLE_MULTI_ADV_INST 0x2e +struct hal_cmd_gatt_client_disable_multi_adv_inst { + int32_t client_if; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_CONFIGURE_BATCHSCAN 0x2f +struct hal_cmd_gatt_client_configure_batchscan { + int32_t client_if; + int32_t full_max; + int32_t trunc_max; + int32_t notify_threshold; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_ENABLE_BATCHSCAN 0x30 +struct hal_cmd_gatt_client_enable_batchscan { + int32_t client_if; + int32_t mode; + int32_t interval; + int32_t window; + int32_t address_type; + int32_t discard_rule; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_DISABLE_BATCHSCAN 0x31 +struct hal_cmd_gatt_client_disable_batchscan { + int32_t client_if; +} __attribute__((packed)); + +#define HAL_OP_GATT_CLIENT_READ_BATCHSCAN_REPORTS 0x32 +struct hal_cmd_gatt_client_read_batchscan_reports { + int32_t client_if; + int32_t scan_mode; +} __attribute__((packed)); + +/* Handsfree client HAL API */ + +#define HAL_OP_HF_CLIENT_CONNECT 0x01 +struct hal_cmd_hf_client_connect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_DISCONNECT 0x02 +struct hal_cmd_hf_client_disconnect { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_CONNECT_AUDIO 0x03 +struct hal_cmd_hf_client_connect_audio { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_DISCONNECT_AUDIO 0x04 +struct hal_cmd_hf_client_disconnect_audio { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_START_VR 0x05 +#define HAL_OP_HF_CLIENT_STOP_VR 0x06 + +#define HF_CLIENT_VOLUME_TYPE_SPEAKER 0x00 +#define HF_CLIENT_VOLUME_TYPE_MIC 0x01 + +#define HAL_OP_HF_CLIENT_VOLUME_CONTROL 0x07 +struct hal_cmd_hf_client_volume_control { + uint8_t type; + uint8_t volume; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_DIAL 0x08 +struct hal_cmd_hf_client_dial { + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_DIAL_MEMORY 0x09 +struct hal_cmd_hf_client_dial_memory { + int32_t location; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_ACTION_CHLD_0 0x00 +#define HAL_HF_CLIENT_ACTION_CHLD_1 0x01 +#define HAL_HF_CLIENT_ACTION_CHLD_2 0x02 +#define HAL_HF_CLIENT_ACTION_CHLD_3 0x03 +#define HAL_HF_CLIENT_ACTION_CHLD_4 0x04 +#define HAL_HF_CLIENT_ACTION_CHLD_1x 0x05 +#define HAL_HF_CLIENT_ACTION_CHLD_2x 0x06 +#define HAL_HF_CLIENT_ACTION_ATA 0x07 +#define HAL_HF_CLIENT_ACTION_CHUP 0x08 +#define HAL_HF_CLIENT_ACTION_BRTH_0 0x09 +#define HAL_HF_CLIENT_ACTION_BRTH_1 0x0a +#define HAL_HF_CLIENT_ACTION_BRTH_2 0x0b + +#define HAL_OP_HF_CLIENT_CALL_ACTION 0x0a +struct hal_cmd_hf_client_call_action { + uint8_t action; + int32_t index; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_QUERY_CURRENT_CALLS 0x0b +#define HAL_OP_HF_CLIENT_QUERY_OPERATOR_NAME 0x0c +#define HAL_OP_HF_CLIENT_RETRIEVE_SUBSCR_INFO 0x0d + +#define HAL_OP_HF_CLIENT_SEND_DTMF 0x0e +struct hal_cmd_hf_client_send_dtmf { + uint8_t tone; +} __attribute__((packed)); + +#define HAL_OP_HF_CLIENT_GET_LAST_VOICE_TAG_NUM 0x0f + +/* MAP CLIENT HAL API */ + +#define HAL_OP_MAP_CLIENT_GET_INSTANCES 0x01 +struct hal_cmd_map_client_get_instances { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +/* Notifications and confirmations */ + +#define HAL_POWER_OFF 0x00 +#define HAL_POWER_ON 0x01 + +#define HAL_EV_ADAPTER_STATE_CHANGED 0x81 +struct hal_ev_adapter_state_changed { + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_ADAPTER_PROPS_CHANGED 0x82 +struct hal_property { + uint8_t type; + uint16_t len; + uint8_t val[0]; +} __attribute__((packed)); +struct hal_ev_adapter_props_changed { + uint8_t status; + uint8_t num_props; + struct hal_property props[0]; +} __attribute__((packed)); + +#define HAL_EV_REMOTE_DEVICE_PROPS 0x83 +struct hal_ev_remote_device_props { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t num_props; + struct hal_property props[0]; +} __attribute__((packed)); + +#define HAL_EV_DEVICE_FOUND 0x84 +struct hal_ev_device_found { + uint8_t num_props; + struct hal_property props[0]; +} __attribute__((packed)); + +#define HAL_DISCOVERY_STATE_STOPPED 0x00 +#define HAL_DISCOVERY_STATE_STARTED 0x01 + +#define HAL_EV_DISCOVERY_STATE_CHANGED 0x85 +struct hal_ev_discovery_state_changed { + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_PIN_REQUEST 0x86 +struct hal_ev_pin_request { + uint8_t bdaddr[6]; + uint8_t name[249]; + uint32_t class_of_dev; +} __attribute__((packed)); + +#define HAL_EV_SSP_REQUEST 0x87 +struct hal_ev_ssp_request { + uint8_t bdaddr[6]; + uint8_t name[249]; + uint32_t class_of_dev; + uint8_t pairing_variant; + uint32_t passkey; +} __attribute__((packed)); + +#define HAL_BOND_STATE_NONE 0 +#define HAL_BOND_STATE_BONDING 1 +#define HAL_BOND_STATE_BONDED 2 + +#define HAL_EV_BOND_STATE_CHANGED 0x88 +struct hal_ev_bond_state_changed { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t state; +} __attribute__((packed)); + +#define HAL_ACL_STATE_CONNECTED 0x00 +#define HAL_ACL_STATE_DISCONNECTED 0x01 + +#define HAL_EV_ACL_STATE_CHANGED 0x89 +struct hal_ev_acl_state_changed { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_DUT_MODE_RECEIVE 0x8a +struct hal_ev_dut_mode_receive { + uint16_t opcode; + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_EV_LE_TEST_MODE 0x8b +struct hal_ev_le_test_mode { + uint8_t status; + uint16_t num_packets; +} __attribute__((packed)); + +#define HAL_EV_ENERGY_INFO 0x8c +struct hal_ev_energy_info { + uint8_t status; + uint8_t ctrl_state; + uint64_t tx_time; + uint64_t rx_time; + uint64_t idle_time; + uint64_t energy_used; +} __attribute__((packed)); + +#define HAL_HIDHOST_STATE_CONNECTED 0x00 +#define HAL_HIDHOST_STATE_CONNECTING 0x01 +#define HAL_HIDHOST_STATE_DISCONNECTED 0x02 +#define HAL_HIDHOST_STATE_DISCONNECTING 0x03 +#define HAL_HIDHOST_STATE_NO_HID 0x07 +#define HAL_HIDHOST_STATE_FAILED 0x08 +#define HAL_HIDHOST_STATE_UNKNOWN 0x09 + +#define HAL_EV_HIDHOST_CONN_STATE 0x81 +struct hal_ev_hidhost_conn_state { + uint8_t bdaddr[6]; + uint8_t state; +} __attribute__((packed)); + +#define HAL_HIDHOST_STATUS_OK 0x00 + +#define HAL_HIDHOST_HS_NOT_READY 0x01 +#define HAL_HIDHOST_HS_INVALID_RAPORT_ID 0x02 +#define HAL_HIDHOST_HS_TRANS_NOT_SUPPORTED 0x03 +#define HAL_HIDHOST_HS_INVALID_PARAM 0x04 +#define HAL_HIDHOST_HS_ERROR 0x05 + +#define HAL_HIDHOST_GENERAL_ERROR 0x06 +#define HAL_HIDHOST_SDP_ERROR 0x07 +#define HAL_HIDHOST_PROTOCOL_ERROR 0x08 +#define HAL_HIDHOST_DB_ERROR 0x09 +#define HAL_HIDHOST_TOD_UNSUPPORTED_ERROR 0x0a +#define HAL_HIDHOST_NO_RESOURCES_ERROR 0x0b +#define HAL_HIDHOST_AUTH_FAILED_ERROR 0x0c +#define HAL_HIDHOST_HDL_ERROR 0x0d + +#define HAL_EV_HIDHOST_INFO 0x82 +struct hal_ev_hidhost_info { + uint8_t bdaddr[6]; + uint8_t attr; + uint8_t subclass; + uint8_t app_id; + uint16_t vendor; + uint16_t product; + uint16_t version; + uint8_t country; + uint16_t descr_len; + uint8_t descr[884]; +} __attribute__((packed)); + +#define HAL_EV_HIDHOST_PROTO_MODE 0x83 +struct hal_ev_hidhost_proto_mode { + uint8_t bdaddr[6]; + uint8_t status; + uint8_t mode; +} __attribute__((packed)); + +#define HAL_EV_HIDHOST_IDLE_TIME 0x84 +struct hal_ev_hidhost_idle_time { + uint8_t bdaddr[6]; + uint8_t status; + uint32_t idle_rate; +} __attribute__((packed)); + +#define HAL_EV_HIDHOST_GET_REPORT 0x85 +struct hal_ev_hidhost_get_report { + uint8_t bdaddr[6]; + uint8_t status; + uint16_t len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_EV_HIDHOST_VIRTUAL_UNPLUG 0x86 +struct hal_ev_hidhost_virtual_unplug { + uint8_t bdaddr[6]; + uint8_t status; +} __attribute__((packed)); + +#define HAL_EV_HIDHOST_HANDSHAKE 0x87 +struct hal_ev_hidhost_handshake { + uint8_t bdaddr[6]; + uint8_t status; +} __attribute__((packed)); + +#define HAL_EV_PAN_CTRL_STATE 0x81 +struct hal_ev_pan_ctrl_state { + uint8_t state; + uint8_t status; + uint8_t local_role; + uint8_t name[17]; +} __attribute__((packed)); + +#define HAL_EV_PAN_CONN_STATE 0x82 +struct hal_ev_pan_conn_state { + uint8_t state; + uint8_t status; + uint8_t bdaddr[6]; + uint8_t local_role; + uint8_t remote_role; +} __attribute__((packed)); + +#define HAL_HEALTH_APP_REG_SUCCESS 0x00 +#define HAL_HEALTH_APP_REG_FAILED 0x01 +#define HAL_HEALTH_APP_DEREG_SUCCESS 0x02 +#define HAL_HEALTH_APP_DEREG_FAILED 0x03 + +#define HAL_HEALTH_CHANNEL_CONNECTING 0x00 +#define HAL_HEALTH_CHANNEL_CONNECTED 0x01 +#define HAL_HEALTH_CHANNEL_DISCONNECTING 0x02 +#define HAL_HEALTH_CHANNEL_DISCONNECTED 0x03 +#define HAL_HEALTH_CHANNEL_DESTROYED 0x04 + +#define HAL_EV_HEALTH_APP_REG_STATE 0x81 +struct hal_ev_health_app_reg_state { + uint16_t id; + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_HEALTH_CHANNEL_STATE 0x82 +struct hal_ev_health_channel_state { + uint16_t app_id; + uint8_t bdaddr[6]; + uint8_t mdep_index; + uint16_t channel_id; + uint8_t channel_state; +} __attribute__((packed)); + +#define HAL_A2DP_STATE_DISCONNECTED 0x00 +#define HAL_A2DP_STATE_CONNECTING 0x01 +#define HAL_A2DP_STATE_CONNECTED 0x02 +#define HAL_A2DP_STATE_DISCONNECTING 0x03 + +#define HAL_EV_A2DP_CONN_STATE 0x81 +struct hal_ev_a2dp_conn_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_AUDIO_SUSPEND 0x00 +#define HAL_AUDIO_STOPPED 0x01 +#define HAL_AUDIO_STARTED 0x02 + +#define HAL_EV_A2DP_AUDIO_STATE 0x82 +struct hal_ev_a2dp_audio_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_A2DP_AUDIO_CONFIG 0x83 +struct hal_ev_a2dp_audio_config { + uint8_t bdaddr[6]; + uint32_t sample_rate; + uint8_t channel_count; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED 0x00 +#define HAL_EV_HANDSFREE_CONN_STATE_CONNECTING 0x01 +#define HAL_EV_HANDSFREE_CONN_STATE_CONNECTED 0x02 +#define HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED 0x03 +#define HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTING 0x04 + +#define HAL_EV_HANDSFREE_CONN_STATE 0x81 +struct hal_ev_handsfree_conn_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED 0x00 +#define HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTING 0x01 +#define HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTED 0x02 +#define HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTING 0x03 + +#define HAL_EV_HANDSFREE_AUDIO_STATE 0x82 +struct hal_ev_handsfree_audio_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_VR_STOPPED 0x00 +#define HAL_HANDSFREE_VR_STARTED 0x01 + +#define HAL_EV_HANDSFREE_VR 0x83 +struct hal_ev_handsfree_vr_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_ANSWER 0x84 +struct hal_ev_handsfree_answer { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_HANGUP 0x85 +struct hal_ev_handsfree_hangup { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_VOLUME 0x86 +struct hal_ev_handsfree_volume { + uint8_t type; + uint8_t volume; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_DIAL 0x87 +struct hal_ev_handsfree_dial { + uint8_t bdaddr[6]; + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_DTMF 0x88 +struct hal_ev_handsfree_dtmf { + uint8_t tone; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_NREC_STOP 0x00 +#define HAL_HANDSFREE_NREC_START 0x01 + +#define HAL_EV_HANDSFREE_NREC 0x89 +struct hal_ev_handsfree_nrec { + uint8_t nrec; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HANDSFREE_CHLD_TYPE_RELEASEHELD 0x00 +#define HAL_HANDSFREE_CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD 0x01 +#define HAL_HANDSFREE_CHLD_TYPE_HOLDACTIVE_ACCEPTHELD 0x02 +#define HAL_HANDSFREE_CHLD_TYPE_ADDHELDTOCONF 0x03 + +#define HAL_EV_HANDSFREE_CHLD 0x8A +struct hal_ev_handsfree_chld { + uint8_t chld; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_CNUM 0x8B +struct hal_ev_handsfree_cnum { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_CIND 0x8C +struct hal_ev_handsfree_cind { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_COPS 0x8D +struct hal_ev_handsfree_cops { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_CLCC 0x8E +struct hal_ev_handsfree_clcc { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_UNKNOWN_AT 0x8F +struct hal_ev_handsfree_unknown_at { + uint8_t bdaddr[6]; + uint16_t len; + uint8_t buf[0]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_HSP_KEY_PRESS 0x90 +struct hal_ev_handsfree_hsp_key_press { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_HANDSFREE_WBS 0x91 +struct hal_ev_handsfree_wbs { + uint8_t wbs; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_AVRCP_FEATURE_NONE 0x00 +#define HAL_AVRCP_FEATURE_METADATA 0x01 +#define HAL_AVRCP_FEATURE_ABSOLUTE_VOLUME 0x02 +#define HAL_AVRCP_FEATURE_BROWSE 0x04 + +#define HAL_EV_AVRCP_REMOTE_FEATURES 0x81 +struct hal_ev_avrcp_remote_features { + uint8_t bdaddr[6]; + uint8_t features; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_GET_PLAY_STATUS 0x82 +#define HAL_EV_AVRCP_LIST_PLAYER_ATTRS 0x83 + +#define HAL_EV_AVRCP_LIST_PLAYER_VALUES 0x84 +struct hal_ev_avrcp_list_player_values { + uint8_t attr; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_GET_PLAYER_VALUES 0x85 +struct hal_ev_avrcp_get_player_values { + uint8_t number; + uint8_t attrs[0]; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_GET_PLAYER_ATTRS_TEXT 0x86 +struct hal_ev_avrcp_get_player_attrs_text { + uint8_t number; + uint8_t attrs[0]; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_GET_PLAYER_VALUES_TEXT 0x87 +struct hal_ev_avrcp_get_player_values_text { + uint8_t attr; + uint8_t number; + uint8_t values[0]; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_SET_PLAYER_VALUES 0x88 +struct hal_ev_avrcp_set_player_values { + uint8_t number; + struct hal_avrcp_player_attr_value attrs[0]; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_GET_ELEMENT_ATTRS 0x89 +struct hal_ev_avrcp_get_element_attrs { + uint8_t number; + uint8_t attrs[0]; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_REGISTER_NOTIFICATION 0x8a +struct hal_ev_avrcp_register_notification { + uint8_t event; + uint32_t param; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_VOLUME_CHANGED 0x8b +struct hal_ev_avrcp_volume_changed { + uint8_t volume; + uint8_t type; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_PASSTHROUGH_CMD 0x8c +struct hal_ev_avrcp_passthrough_cmd { + uint8_t id; + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_CTRL_CONN_STATE 0x81 +struct hal_ev_avrcp_ctrl_conn_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_AVRCP_CTRL_PASSTHROUGH_RSP 0x82 +struct hal_ev_avrcp_ctrl_passthrough_rsp { + uint8_t id; + uint8_t key_state; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_REGISTER_CLIENT 0x81 +struct hal_ev_gatt_client_register_client { + int32_t status; + int32_t client_if; + uint8_t app_uuid[16]; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_SCAN_RESULT 0x82 +struct hal_ev_gatt_client_scan_result { + uint8_t bda[6]; + int32_t rssi; + uint16_t len; + uint8_t adv_data[0]; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_CONNECT 0x83 +struct hal_ev_gatt_client_connect { + int32_t conn_id; + int32_t status; + int32_t client_if; + uint8_t bda[6]; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_DISCONNECT 0x84 +struct hal_ev_gatt_client_disconnect { + int32_t conn_id; + int32_t status; + int32_t client_if; + uint8_t bda[6]; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_SEARCH_COMPLETE 0x85 +struct hal_ev_gatt_client_search_complete { + int32_t conn_id; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_SEARCH_RESULT 0x86 +struct hal_ev_gatt_client_search_result { + int32_t conn_id; + struct hal_gatt_srvc_id srvc_id; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_GET_CHARACTERISTIC 0x87 +struct hal_ev_gatt_client_get_characteristic { + int32_t conn_id; + int32_t status; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + int32_t char_prop; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_GET_DESCRIPTOR 0x88 +struct hal_ev_gatt_client_get_descriptor { + int32_t conn_id; + int32_t status; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + struct hal_gatt_gatt_id descr_id; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_GET_INC_SERVICE 0X89 +struct hal_ev_gatt_client_get_inc_service { + int32_t conn_id; + int32_t status; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_srvc_id incl_srvc_id; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_REGISTER_FOR_NOTIF 0x8a +struct hal_ev_gatt_client_reg_for_notif { + int32_t conn_id; + int32_t registered; + int32_t status; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_NOTIFY 0x8b +struct hal_ev_gatt_client_notify { + int32_t conn_id; + uint8_t bda[6]; + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + uint8_t is_notify; + uint16_t len; + uint8_t value[0]; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_READ_CHARACTERISTIC 0x8c +struct hal_gatt_read_params { + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + struct hal_gatt_gatt_id descr_id; + uint8_t status; + uint16_t value_type; + uint16_t len; + uint8_t value[0]; +} __attribute__((packed)); + +struct hal_ev_gatt_client_read_characteristic { + int32_t conn_id; + int32_t status; + struct hal_gatt_read_params data; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_WRITE_CHARACTERISTIC 0x8d +struct hal_gatt_write_params { + struct hal_gatt_srvc_id srvc_id; + struct hal_gatt_gatt_id char_id; + struct hal_gatt_gatt_id descr_id; + uint8_t status; +} __attribute__((packed)); + +struct hal_ev_gatt_client_write_characteristic { + int32_t conn_id; + int32_t status; + struct hal_gatt_write_params data; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_READ_DESCRIPTOR 0x8e +struct hal_ev_gatt_client_read_descriptor { + int32_t conn_id; + int32_t status; + struct hal_gatt_read_params data; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_WRITE_DESCRIPTOR 0x8f +struct hal_ev_gatt_client_write_descriptor { + int32_t conn_id; + int32_t status; + struct hal_gatt_write_params data; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_EXEC_WRITE 0x90 +struct hal_ev_gatt_client_exec_write { + int32_t conn_id; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_READ_REMOTE_RSSI 0x91 +struct hal_ev_gatt_client_read_remote_rssi { + int32_t client_if; + uint8_t address[6]; + int32_t rssi; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_LISTEN 0x92 +struct hal_ev_gatt_client_listen { + int32_t status; + int32_t server_if; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_REGISTER 0x93 +struct hal_ev_gatt_server_register { + int32_t status; + int32_t server_if; + uint8_t uuid[16]; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_CONNECTION 0x94 +struct hal_ev_gatt_server_connection { + int32_t conn_id; + int32_t server_if; + int32_t connected; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_SERVICE_ADDED 0x95 +struct hal_ev_gatt_server_service_added { + int32_t status; + int32_t server_if; + struct hal_gatt_srvc_id srvc_id; + int32_t srvc_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_INC_SRVC_ADDED 0x96 +struct hal_ev_gatt_server_inc_srvc_added { + int32_t status; + int32_t server_if; + int32_t srvc_handle; + int32_t incl_srvc_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_CHAR_ADDED 0x97 +struct hal_ev_gatt_server_characteristic_added { + int32_t status; + int32_t server_if; + uint8_t uuid[16]; + int32_t srvc_handle; + int32_t char_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_DESCRIPTOR_ADDED 0x98 +struct hal_ev_gatt_server_descriptor_added { + int32_t status; + int32_t server_if; + uint8_t uuid[16]; + int32_t srvc_handle; + int32_t descr_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_SERVICE_STARTED 0x99 +struct hal_ev_gatt_server_service_started { + int32_t status; + int32_t server_if; + int32_t srvc_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_SERVICE_STOPPED 0x9a +struct hal_ev_gatt_server_service_stopped { + int32_t status; + int32_t server_if; + int32_t srvc_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_SERVICE_DELETED 0x9b +struct hal_ev_gatt_server_service_deleted { + int32_t status; + int32_t server_if; + int32_t srvc_handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_REQUEST_READ 0x9c +struct hal_ev_gatt_server_request_read { + int32_t conn_id; + int32_t trans_id; + uint8_t bdaddr[6]; + int32_t attr_handle; + int32_t offset; + uint8_t is_long; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_REQUEST_WRITE 0x9d +struct hal_ev_gatt_server_request_write { + int32_t conn_id; + int32_t trans_id; + uint8_t bdaddr[6]; + int32_t attr_handle; + int32_t offset; + int32_t length; + uint8_t need_rsp; + uint8_t is_prep; + uint8_t value[0]; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_REQUEST_EXEC_WRITE 0x9e +struct hal_ev_gatt_server_request_exec_write { + int32_t conn_id; + int32_t trans_id; + uint8_t bdaddr[6]; + int32_t exec_write; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_RSP_CONFIRMATION 0x9f +struct hal_ev_gatt_server_rsp_confirmation { + int32_t status; + int32_t handle; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_CONFIGURE_MTU 0xa0 +struct hal_ev_gatt_client_configure_mtu { + int32_t conn_id; + int32_t status; + int32_t mtu; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_FILTER_CONFIG 0xa1 +struct hal_ev_gatt_client_filter_config { + int32_t action; + int32_t client_if; + int32_t status; + int32_t type; + int32_t space; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_FILTER_PARAMS 0xa2 +struct hal_ev_gatt_client_filter_params { + int32_t action; + int32_t client_if; + int32_t status; + int32_t space; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_FILTER_STATUS 0xa3 +struct hal_ev_gatt_client_filter_status { + int32_t enable; + int32_t client_if; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_MULTI_ADV_ENABLE 0xa4 +struct hal_ev_gatt_client_multi_adv_enable { + int32_t client_if; + int32_t status; +} __attribute__((packed)); + + +#define HAL_EV_GATT_CLIENT_MULTI_ADV_UPDATE 0xa5 +struct hal_ev_gatt_client_multi_adv_update { + int32_t client_if; + int32_t status; +} __attribute__((packed)); + + +#define HAL_EV_GATT_CLIENT_MULTI_ADV_DATA 0xa6 +struct hal_ev_gatt_client_multi_adv_data { + int32_t client_if; + int32_t status; +} __attribute__((packed)); + + +#define HAL_EV_GATT_CLIENT_MULTI_ADV_DISABLE 0xa7 +struct hal_ev_gatt_client_multi_adv_disable { + int32_t client_if; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_CONGESTION 0xa8 +struct hal_ev_gatt_client_congestion { + int32_t conn_id; + uint8_t congested; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_CONFIG_BATCHSCAN 0xa9 +struct hal_ev_gatt_client_config_batchscan { + int32_t client_if; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_ENABLE_BATCHSCAN 0xaa +struct hal_ev_gatt_client_enable_batchscan { + int32_t action; + int32_t client_if; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_BATCHSCAN_REPORTS 0xab +struct hal_ev_gatt_client_batchscan_reports { + int32_t client_if; + int32_t status; + int32_t format; + int32_t num; + int32_t data_len; + uint8_t data[0]; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_BATCHSCAN_THRESHOLD 0xac +struct hal_ev_gatt_client_batchscan_threshold { + int32_t client_if; +} __attribute__((packed)); + +#define HAL_EV_GATT_CLIENT_TRACK_ADV 0xad +struct hal_ev_gatt_client_track_adv { + int32_t client_if; + int32_t filetr_index; + int32_t address_type; + uint8_t address[6]; + int32_t state; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_INDICATION_SENT 0xae +struct hal_ev_gatt_server_indication_sent { + int32_t conn_id; + int32_t status; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_CONGESTION 0xaf +struct hal_ev_gatt_server_congestion { + int32_t conn_id; + uint8_t congested; +} __attribute__((packed)); + +#define HAL_EV_GATT_SERVER_MTU_CHANGED 0xb0 +struct hal_ev_gatt_server_mtu_changed { + int32_t conn_id; + int32_t mtu; +} __attribute__((packed)); + +#define HAL_GATT_PERMISSION_READ 0x0001 +#define HAL_GATT_PERMISSION_READ_ENCRYPTED 0x0002 +#define HAL_GATT_PERMISSION_READ_ENCRYPTED_MITM 0x0004 +#define HAL_GATT_PERMISSION_WRITE 0x0010 +#define HAL_GATT_PERMISSION_WRITE_ENCRYPTED 0x0020 +#define HAL_GATT_PERMISSION_WRITE_ENCRYPTED_MITM 0x0040 +#define HAL_GATT_PERMISSION_WRITE_SIGNED 0x0080 +#define HAL_GATT_PERMISSION_WRITE_SIGNED_MITM 0x0100 + +#define HAL_GATT_AUTHENTICATION_NONE 0 +#define HAL_GATT_AUTHENTICATION_NO_MITM 1 +#define HAL_GATT_AUTHENTICATION_MITM 2 + +#define HAL_HF_CLIENT_CONN_STATE_DISCONNECTED 0x00 +#define HAL_HF_CLIENT_CONN_STATE_CONNECTING 0x01 +#define HAL_HF_CLIENT_CONN_STATE_CONNECTED 0x02 +#define HAL_HF_CLIENT_CONN_STATE_SLC_CONNECTED 0x03 +#define HAL_HF_CLIENT_CONN_STATE_DISCONNECTING 0x04 + +#define HAL_HF_CLIENT_PEER_FEAT_3WAY 0x00000001 +#define HAL_HF_CLIENT_PEER_FEAT_ECNR 0x00000002 +#define HAL_HF_CLIENT_PEER_FEAT_VREC 0x00000004 +#define HAL_HF_CLIENT_PEER_FEAT_INBAND 0x00000008 +#define HAL_HF_CLIENT_PEER_FEAT_VTAG 0x00000010 +#define HAL_HF_CLIENT_PEER_FEAT_REJECT 0x00000020 +#define HAL_HF_CLIENT_PEER_FEAT_ECS 0x00000040 +#define HAL_HF_CLIENT_PEER_FEAT_ECC 0x00000080 +#define HAL_HF_CLIENT_PEER_FEAT_EXTERR 0x00000100 +#define HAL_HF_CLIENT_PEER_FEAT_CODEC 0x00000200 + +#define HAL_HF_CLIENT_CHLD_FEAT_REL 0x00000001 +#define HAL_HF_CLIENT_CHLD_FEAT_REL_ACC 0x00000002 +#define HAL_HF_CLIENT_CHLD_FEAT_REL_X 0x00000004 +#define HAL_HF_CLIENT_CHLD_FEAT_HOLD_ACC 0x00000008 +#define HAL_HF_CLIENT_CHLD_FEAT_PRIV_X 0x00000010 +#define HAL_HF_CLIENT_CHLD_FEAT_MERGE 0x00000020 +#define HAL_HF_CLIENT_CHLD_FEAT_MERGE_DETACH 0x00000040 + +#define HAL_EV_HF_CLIENT_CONN_STATE 0x81 +struct hal_ev_hf_client_conn_state { + uint8_t state; + uint32_t peer_feat; + uint32_t chld_feat; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED 0x00 +#define HAL_HF_CLIENT_AUDIO_STATE_CONNECTING 0x01 +#define HAL_HF_CLIENT_AUDIO_STATE_CONNECTED 0x02 +#define HAL_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC 0x03 + +#define HAL_EV_HF_CLIENT_AUDIO_STATE 0x82 +struct hal_ev_hf_client_audio_state { + uint8_t state; + uint8_t bdaddr[6]; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_VR_STOPPED 0x00 +#define HAL_HF_CLIENT_VR_STARTED 0x01 + +#define HAL_EV_HF_CLIENT_VR_STATE 0x83 +struct hal_ev_hf_client_vr_state { + uint8_t state; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_NET_NOT_AVAILABLE 0x00 +#define HAL_HF_CLIENT_NET_AVAILABLE 0x01 + +#define HAL_EV_HF_CLIENT_NET_STATE 0x84 +struct hal_ev_hf_client_net_state { + uint8_t state; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_NET_ROAMING_TYPE_HOME 0x00 +#define HAL_HF_CLIENT_NET_ROAMING_TYPE_ROAMING 0x01 + +#define HAL_EV_HF_CLIENT_NET_ROAMING_TYPE 0x85 +struct hal_ev_hf_client_net_roaming_type { + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_HF_CLIENT_NET_SIGNAL_STRENGTH 0x86 +struct hal_ev_hf_client_net_signal_strength { + uint8_t signal_strength; +} __attribute__((packed)); + +#define HAL_EV_HF_CLIENT_BATTERY_LEVEL 0x87 +struct hal_ev_hf_client_battery_level { + uint8_t battery_level; +} __attribute__((packed)); + +#define HAL_EV_HF_CLIENT_OPERATOR_NAME 0x88 +struct hal_ev_hf_client_operator_name { + uint16_t name_len; + uint8_t name[0]; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_CALL_IND_NO_CALL_IN_PROGERSS 0x00 +#define HAL_HF_CLIENT_CALL_IND_CALL_IN_PROGERSS 0x01 + +#define HAL_EV_HF_CLIENT_CALL_INDICATOR 0x89 +struct hal_ev_hf_client_call_indicator { + uint8_t call; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_CALL_SETUP_NONE 0x00 +#define HAL_HF_CLIENT_CALL_SETUP_INCOMING 0x01 +#define HAL_HF_CLIENT_CALL_SETUP_OUTGOING 0x02 +#define HAL_HF_CLIENT_CALL_SETUP_ALERTING 0x03 + +#define HAL_EV_HF_CLIENT_CALL_SETUP_INDICATOR 0x8a +struct hal_ev_hf_client_call_setup_indicator { + uint8_t call_setup; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_CALL_HELD_IND_NONE 0x00 +#define HAL_HF_CLIENT_CALL_HELD_IND_HOLD_AND_ACTIVE 0x01 +#define HAL_HF_CLIENT_CALL_SETUP_IND_HOLD 0x02 + +#define HAL_EV_HF_CLIENT_CALL_HELD_INDICATOR 0x8b +struct hal_ev_hf_client_call_held_indicator { + uint8_t call_held; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_RESP_AND_HOLD_STATUS_HELD 0x00 +#define HAL_HF_CLIENT_RESP_AND_HOLD_STATUS_ACCEPT 0x01 +#define HAL_HF_CLIENT_RESP_AND_HOLD_STATUS_REJECT 0x02 + +#define HAL_EV_HF_CLIENT_RESPONSE_AND_HOLD_STATUS 0x8c +struct hal_ev_hf_client_response_and_hold_status { + uint8_t status; +} __attribute__((packed)); + +#define HAL_EV_HF_CLIENT_CALLING_LINE_IDENT 0x8d +struct hal_ev_hf_client_calling_line_ident { + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_EV_HF_CLIENT_CALL_WAITING 0x8e +struct hal_ev_hf_client_call_waiting { + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_DIRECTION_OUTGOING 0x00 +#define HAL_HF_CLIENT_DIRECTION_INCOMING 0x01 + +#define HAL_HF_CLIENT_CALL_STATE_ACTIVE 0x00 +#define HAL_HF_CLIENT_CALL_STATE_HELD 0x01 +#define HAL_HF_CLIENT_CALL_STATE_DIALING 0x02 +#define HAL_HF_CLIENT_CALL_STATE_ALERTING 0x03 +#define HAL_HF_CLIENT_CALL_STATE_INCOMING 0x04 +#define HAL_HF_CLIENT_CALL_STATE_WAITING 0x05 +#define HAL_HF_CLIENT_CALL_STATE_HELD_BY_RESP_AND_HOLD 0x06 + +#define HAL_EV_HF_CLIENT_CURRENT_CALL 0x8f +struct hal_ev_hf_client_current_call { + uint8_t index; + uint8_t direction; + uint8_t call_state; + uint8_t multiparty; + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_EV_CLIENT_VOLUME_CHANGED 0x90 +struct hal_ev_hf_client_volume_changed { + uint8_t type; + uint8_t volume; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_CMD_COMP_OK 0x00 +#define HAL_HF_CLIENT_CMD_COMP_ERR 0x01 +#define HAL_HF_CLIENT_CMD_COMP_ERR_NO_CARRIER 0x02 +#define HAL_HF_CLIENT_CMD_COMP_ERR_BUSY 0x03 +#define HAL_HF_CLIENT_CMD_COMP_ERR_NO_ANSWER 0x04 +#define HAL_HF_CLIENT_CMD_COMP_ERR_DELAYED 0x05 +#define HAL_HF_CLIENT_CMD_COMP_ERR_BACKLISTED 0x06 +#define HAL_HF_CLIENT_CMD_COMP_ERR_CME 0x07 + +#define HAL_EV_CLIENT_COMMAND_COMPLETE 0x91 +struct hal_ev_hf_client_command_complete { + uint8_t type; + uint8_t cme; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_SUBSCR_TYPE_UNKNOWN 0x00 +#define HAL_HF_CLIENT_SUBSCR_TYPE_VOICE 0x01 +#define HAL_HF_CLIENT_SUBSCR_TYPE_FAX 0x02 + +#define HAL_EV_CLIENT_SUBSCRIBER_SERVICE_INFO 0x92 +struct hal_ev_hf_client_subscriber_service_info { + uint8_t type; + uint16_t name_len; + uint8_t name[0]; +} __attribute__((packed)); + +#define HAL_HF_CLIENT_INBAND_RINGTONE_NOT_PROVIDED 0x00 +#define HAL_HF_CLIENT_INBAND_RINGTONE_PROVIDED 0x01 + +#define HAL_EV_CLIENT_INBAND_SETTINGS 0x93 +struct hal_ev_hf_client_inband_settings { + uint8_t state; +} __attribute__((packed)); + +#define HAL_EV_CLIENT_LAST_VOICE_CALL_TAG_NUM 0x94 +struct hal_ev_hf_client_last_void_call_tag_num { + uint16_t number_len; + uint8_t number[0]; +} __attribute__((packed)); + +#define HAL_EV_CLIENT_RING_INDICATION 0x95 + +#define HAL_EV_MAP_CLIENT_REMOTE_MAS_INSTANCES 0x81 +struct hal_map_client_mas_instance { + int32_t id; + int32_t scn; + int32_t msg_types; + int32_t name_len; + uint8_t name[0]; +} __attribute__((packed)); + +struct hal_ev_map_client_remote_mas_instances { + int8_t status; + uint8_t bdaddr[6]; + int32_t num_instances; + struct hal_map_client_mas_instance instances[0]; +} __attribute__((packed));
diff --git a/android/hal-pan.c b/android/hal-pan.c new file mode 100644 index 0000000..61d44a9 --- /dev/null +++ b/android/hal-pan.c
@@ -0,0 +1,210 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "hal-utils.h" +#include "hal-log.h" +#include "hal.h" +#include "hal-msg.h" +#include "hal-ipc.h" + +static const btpan_callbacks_t *cbs = NULL; + +static bool interface_ready(void) +{ + return cbs != NULL; +} + +static void handle_conn_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_pan_conn_state *ev = buf; + + if (cbs->connection_state_cb) + cbs->connection_state_cb(ev->state, ev->status, + (bt_bdaddr_t *) ev->bdaddr, + ev->local_role, ev->remote_role); +} + +static void handle_ctrl_state(void *buf, uint16_t len, int fd) +{ + struct hal_ev_pan_ctrl_state *ev = buf; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + if (cbs->control_state_cb) + cbs->control_state_cb(ev->state, ev->local_role, ev->status, + (char *)ev->name); +#else + /* + * Callback declared in bt_pan.h is 'typedef void + * (*btpan_control_state_callback)(btpan_control_state_t state, + * bt_status_t error, int local_role, const char* ifname); + * But PanService.Java defined it wrong way. + * private void onControlStateChanged(int local_role, int state, + * int error, String ifname). + * First and third parameters are misplaced, so sending data according + * to PanService.Java. + */ + if (cbs->control_state_cb) + cbs->control_state_cb(ev->local_role, ev->state, ev->status, + (char *)ev->name); +#endif +} + +/* + * handlers will be called from notification thread context, + * index in table equals to 'opcode - HAL_MINIMUM_EVENT' + */ +static const struct hal_ipc_handler ev_handlers[] = { + /* HAL_EV_PAN_CTRL_STATE */ + { handle_ctrl_state, false, sizeof(struct hal_ev_pan_ctrl_state) }, + /* HAL_EV_PAN_CONN_STATE */ + { handle_conn_state, false, sizeof(struct hal_ev_pan_conn_state) }, +}; + +static bt_status_t pan_enable(int local_role) +{ + struct hal_cmd_pan_enable cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + cmd.local_role = local_role; + + return hal_ipc_cmd(HAL_SERVICE_ID_PAN, HAL_OP_PAN_ENABLE, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static int pan_get_local_role(void) +{ + struct hal_rsp_pan_get_role rsp; + size_t len = sizeof(rsp); + bt_status_t status; + + DBG(""); + + if (!interface_ready()) + return BTPAN_ROLE_NONE; + + status = hal_ipc_cmd(HAL_SERVICE_ID_PAN, HAL_OP_PAN_GET_ROLE, 0, NULL, + &len, &rsp, NULL); + if (status != BT_STATUS_SUCCESS) + return BTPAN_ROLE_NONE; + + return rsp.local_role; +} + +static bt_status_t pan_connect(const bt_bdaddr_t *bd_addr, int local_role, + int remote_role) +{ + struct hal_cmd_pan_connect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + cmd.local_role = local_role; + cmd.remote_role = remote_role; + + return hal_ipc_cmd(HAL_SERVICE_ID_PAN, HAL_OP_PAN_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t pan_disconnect(const bt_bdaddr_t *bd_addr) +{ + struct hal_cmd_pan_disconnect cmd; + + DBG(""); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_PAN, HAL_OP_PAN_DISCONNECT, + sizeof(cmd), &cmd, NULL, NULL, NULL); +} + +static bt_status_t pan_init(const btpan_callbacks_t *callbacks) +{ + struct hal_cmd_register_module cmd; + int ret; + + DBG(""); + + if (interface_ready()) + return BT_STATUS_DONE; + + cbs = callbacks; + + hal_ipc_register(HAL_SERVICE_ID_PAN, ev_handlers, + sizeof(ev_handlers)/sizeof(ev_handlers[0])); + + cmd.service_id = HAL_SERVICE_ID_PAN; + cmd.mode = HAL_MODE_DEFAULT; + cmd.max_clients = 1; + + ret = hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + if (ret != BT_STATUS_SUCCESS) { + cbs = NULL; + hal_ipc_unregister(HAL_SERVICE_ID_PAN); + } + + return ret; +} + +static void pan_cleanup(void) +{ + struct hal_cmd_unregister_module cmd; + + DBG(""); + + if (!interface_ready()) + return; + + cmd.service_id = HAL_SERVICE_ID_PAN; + + hal_ipc_cmd(HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + sizeof(cmd), &cmd, NULL, NULL, NULL); + + hal_ipc_unregister(HAL_SERVICE_ID_PAN); + + cbs = NULL; +} + +static btpan_interface_t pan_if = { + .size = sizeof(pan_if), + .init = pan_init, + .enable = pan_enable, + .get_local_role = pan_get_local_role, + .connect = pan_connect, + .disconnect = pan_disconnect, + .cleanup = pan_cleanup +}; + +btpan_interface_t *bt_get_pan_interface(void) +{ + return &pan_if; +}
diff --git a/android/hal-sco.c b/android/hal-sco.c new file mode 100644 index 0000000..2c95866 --- /dev/null +++ b/android/hal-sco.c
@@ -0,0 +1,1530 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <errno.h> +#include <pthread.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include <hardware/audio.h> +#include <hardware/hardware.h> +#include <audio_utils/resampler.h> + +#include "hal-utils.h" +#include "sco-msg.h" +#include "ipc-common.h" +#include "hal-log.h" +#include "hal.h" + +#define AUDIO_STREAM_DEFAULT_RATE 44100 +#define AUDIO_STREAM_SCO_RATE 8000 +#define AUDIO_STREAM_DEFAULT_FORMAT AUDIO_FORMAT_PCM_16_BIT + +#define OUT_BUFFER_SIZE 2560 +#define OUT_STREAM_FRAMES 2560 +#define IN_STREAM_FRAMES 5292 + +#define SOCKET_POLL_TIMEOUT_MS 500 + +static int listen_sk = -1; +static int ipc_sk = -1; + +static int sco_fd = -1; +static uint16_t sco_mtu = 0; +static pthread_mutex_t sco_mutex = PTHREAD_MUTEX_INITIALIZER; + +static pthread_t ipc_th = 0; +static pthread_mutex_t sk_mutex = PTHREAD_MUTEX_INITIALIZER; + +static struct sco_stream_in *sco_stream_in = NULL; +static struct sco_stream_out *sco_stream_out = NULL; + +struct sco_audio_config { + uint32_t rate; + uint32_t channels; + uint32_t frame_num; + audio_format_t format; +}; + +struct sco_stream_out { + struct audio_stream_out stream; + + struct sco_audio_config cfg; + + uint8_t *downmix_buf; + uint8_t *cache; + size_t cache_len; + + size_t samples; + struct timespec start; + + struct resampler_itfe *resampler; + int16_t *resample_buf; + uint32_t resample_frame_num; + + bt_bdaddr_t bd_addr; +}; + +static void sco_close_socket(void) +{ + DBG("sco fd %d", sco_fd); + + if (sco_fd < 0) + return; + + shutdown(sco_fd, SHUT_RDWR); + close(sco_fd); + sco_fd = -1; +} + +struct sco_stream_in { + struct audio_stream_in stream; + + struct sco_audio_config cfg; + + struct resampler_itfe *resampler; + int16_t *resample_buf; + uint32_t resample_frame_num; + + bt_bdaddr_t bd_addr; +}; + +struct sco_dev { + struct audio_hw_device dev; + struct sco_stream_out *out; + struct sco_stream_in *in; +}; + +/* + * return the minimum frame numbers from resampling between BT stack's rate + * and audio flinger's. For output stream, 'output' shall be true, otherwise + * false for input streams at audio flinger side. + */ +static size_t get_resample_frame_num(uint32_t sco_rate, uint32_t rate, + size_t frame_num, bool output) +{ + size_t resample_frames_num = frame_num * sco_rate / rate + output; + + DBG("resampler: sco_rate %d frame_num %zd rate %d resample frames %zd", + sco_rate, frame_num, rate, resample_frames_num); + + return resample_frames_num; +} + +/* SCO IPC functions */ + +static int sco_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len, + void *param, size_t *rsp_len, void *rsp, int *fd) +{ + ssize_t ret; + struct msghdr msg; + struct iovec iv[2]; + struct ipc_hdr cmd; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + struct ipc_status s; + size_t s_len = sizeof(s); + + pthread_mutex_lock(&sk_mutex); + + if (ipc_sk < 0) { + error("sco: Invalid cmd socket passed to sco_ipc_cmd"); + goto failed; + } + + if (!rsp || !rsp_len) { + memset(&s, 0, s_len); + rsp_len = &s_len; + rsp = &s; + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmd, 0, sizeof(cmd)); + + cmd.service_id = service_id; + cmd.opcode = opcode; + cmd.len = len; + + iv[0].iov_base = &cmd; + iv[0].iov_len = sizeof(cmd); + + iv[1].iov_base = param; + iv[1].iov_len = len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + ret = sendmsg(ipc_sk, &msg, 0); + if (ret < 0) { + error("sco: Sending command failed:%s", strerror(errno)); + goto failed; + } + + /* socket was shutdown */ + if (ret == 0) { + error("sco: Command socket closed"); + goto failed; + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmd, 0, sizeof(cmd)); + + iv[0].iov_base = &cmd; + iv[0].iov_len = sizeof(cmd); + + iv[1].iov_base = rsp; + iv[1].iov_len = *rsp_len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + if (fd) { + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + } + + ret = recvmsg(ipc_sk, &msg, 0); + if (ret < 0) { + error("sco: Receiving command response failed:%s", + strerror(errno)); + goto failed; + } + + if (ret < (ssize_t) sizeof(cmd)) { + error("sco: Too small response received(%zd bytes)", ret); + goto failed; + } + + if (cmd.service_id != service_id) { + error("sco: Invalid service id (%u vs %u)", cmd.service_id, + service_id); + goto failed; + } + + if (ret != (ssize_t) (sizeof(cmd) + cmd.len)) { + error("sco: Malformed response received(%zd bytes)", ret); + goto failed; + } + + if (cmd.opcode != opcode && cmd.opcode != SCO_OP_STATUS) { + error("sco: Invalid opcode received (%u vs %u)", + cmd.opcode, opcode); + goto failed; + } + + if (cmd.opcode == SCO_OP_STATUS) { + struct ipc_status *s = rsp; + + if (sizeof(*s) != cmd.len) { + error("sco: Invalid status length"); + goto failed; + } + + if (s->code == SCO_STATUS_SUCCESS) { + error("sco: Invalid success status response"); + goto failed; + } + + pthread_mutex_unlock(&sk_mutex); + + return s->code; + } + + pthread_mutex_unlock(&sk_mutex); + + /* Receive auxiliary data in msg */ + if (fd) { + struct cmsghdr *cmsg; + + *fd = -1; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); + break; + } + } + + if (*fd < 0) + goto failed; + } + + *rsp_len = cmd.len; + + return SCO_STATUS_SUCCESS; + +failed: + /* Some serious issue happen on IPC - recover */ + shutdown(ipc_sk, SHUT_RDWR); + pthread_mutex_unlock(&sk_mutex); + + return SCO_STATUS_FAILED; +} + +static int ipc_get_sco_fd(bt_bdaddr_t *bd_addr) +{ + int ret = SCO_STATUS_SUCCESS; + + pthread_mutex_lock(&sco_mutex); + + if (sco_fd < 0) { + struct sco_cmd_get_fd cmd; + struct sco_rsp_get_fd rsp; + size_t rsp_len = sizeof(rsp); + + DBG("Getting SCO fd"); + + memcpy(cmd.bdaddr, bd_addr, sizeof(cmd.bdaddr)); + + ret = sco_ipc_cmd(SCO_SERVICE_ID, SCO_OP_GET_FD, sizeof(cmd), + &cmd, &rsp_len, &rsp, &sco_fd); + + /* Sometimes mtu returned is wrong */ + sco_mtu = /* rsp.mtu */ 48; + } + + pthread_mutex_unlock(&sco_mutex); + + return ret; +} + +/* Audio stream functions */ + +static void downmix_to_mono(struct sco_stream_out *out, const uint8_t *buffer, + size_t frame_num) +{ + const int16_t *input = (const void *) buffer; + int16_t *output = (void *) out->downmix_buf; + size_t i; + + for (i = 0; i < frame_num; i++) { + int16_t l = get_le16(&input[i * 2]); + int16_t r = get_le16(&input[i * 2 + 1]); + + put_le16((l + r) / 2, &output[i]); + } +} + +static uint64_t timespec_diff_us(struct timespec *a, struct timespec *b) +{ + struct timespec res; + + res.tv_sec = a->tv_sec - b->tv_sec; + res.tv_nsec = a->tv_nsec - b->tv_nsec; + + if (res.tv_nsec < 0) { + res.tv_sec--; + res.tv_nsec += 1000000000ll; /* 1sec */ + } + + return res.tv_sec * 1000000ll + res.tv_nsec / 1000ll; +} + +static bool write_data(struct sco_stream_out *out, const uint8_t *buffer, + size_t bytes) +{ + struct pollfd pfd; + size_t len, written = 0; + int ret; + uint8_t *p; + uint64_t audio_sent_us, audio_passed_us; + + pfd.fd = sco_fd; + pfd.events = POLLOUT | POLLHUP | POLLNVAL; + + while (bytes > written) { + struct timespec now; + + /* poll for sending */ + if (poll(&pfd, 1, SOCKET_POLL_TIMEOUT_MS) == 0) { + DBG("timeout fd %d", sco_fd); + return false; + } + + if (pfd.revents & (POLLHUP | POLLNVAL)) { + error("error fd %d, events 0x%x", sco_fd, pfd.revents); + return false; + } + + len = bytes - written > sco_mtu ? sco_mtu : bytes - written; + + clock_gettime(CLOCK_REALTIME, &now); + /* Mark start of the stream */ + if (!out->samples) + memcpy(&out->start, &now, sizeof(out->start)); + + audio_sent_us = out->samples * 1000000ll / AUDIO_STREAM_SCO_RATE; + audio_passed_us = timespec_diff_us(&now, &out->start); + if ((int) (audio_sent_us - audio_passed_us) > 1500) { + struct timespec timeout = {0, + (audio_sent_us - + audio_passed_us) * 1000}; + DBG("Sleeping for %d ms", + (int) (audio_sent_us - audio_passed_us)); + nanosleep(&timeout, NULL); + } else if ((int)(audio_passed_us - audio_sent_us) > 50000) { + DBG("\n\nResync\n\n"); + out->samples = 0; + memcpy(&out->start, &now, sizeof(out->start)); + } + + if (out->cache_len) { + DBG("First packet cache_len %zd", out->cache_len); + memcpy(out->cache + out->cache_len, buffer, + sco_mtu - out->cache_len); + p = out->cache; + } else { + if (bytes - written >= sco_mtu) + p = (void *) buffer + written; + else { + memcpy(out->cache, buffer + written, + bytes - written); + out->cache_len = bytes - written; + DBG("Last packet, cache %zd bytes", + bytes - written); + written += bytes - written; + continue; + } + } + + ret = write(sco_fd, p, len); + if (ret > 0) { + if (out->cache_len) { + written = sco_mtu - out->cache_len; + out->cache_len = 0; + } else + written += ret; + + out->samples += ret / 2; + + DBG("written %d samples %zd total %zd bytes", + ret, out->samples, written); + continue; + } + + if (errno == EAGAIN) { + ret = errno; + warn("write failed (%d)", ret); + continue; + } + + if (errno != EINTR) { + ret = errno; + error("write failed (%d) fd %d bytes %zd", ret, sco_fd, + bytes); + return false; + } + } + + DBG("written %zd bytes", bytes); + + return true; +} + +static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, + size_t bytes) +{ + struct sco_stream_out *out = (struct sco_stream_out *) stream; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + size_t frame_num = bytes / audio_stream_out_frame_size(stream); +#else + size_t frame_num = bytes / audio_stream_frame_size(&out->stream.common); +#endif + size_t output_frame_num = frame_num; + void *send_buf = out->downmix_buf; + size_t total; + + DBG("write to fd %d bytes %zu", sco_fd, bytes); + + if (ipc_get_sco_fd(&out->bd_addr) != SCO_STATUS_SUCCESS) + return -1; + + if (!out->downmix_buf) { + error("sco: downmix buffer not initialized"); + return -1; + } + + downmix_to_mono(out, buffer, frame_num); + + if (out->resampler) { + int ret; + + /* limit resampler's output within what resample buf can hold */ + output_frame_num = out->resample_frame_num; + + ret = out->resampler->resample_from_input(out->resampler, + send_buf, + &frame_num, + out->resample_buf, + &output_frame_num); + if (ret) { + error("Failed to resample frames: %zd input %zd (%s)", + frame_num, output_frame_num, strerror(ret)); + return -1; + } + + send_buf = out->resample_buf; + + DBG("Resampled: frame_num %zd, output_frame_num %zd", + frame_num, output_frame_num); + } + + total = output_frame_num * sizeof(int16_t) * 1; + + DBG("total %zd", total); + + if (!write_data(out, send_buf, total)) + return -1; + + return bytes; +} + +static uint32_t out_get_sample_rate(const struct audio_stream *stream) +{ + struct sco_stream_out *out = (struct sco_stream_out *) stream; + + DBG("rate %u", out->cfg.rate); + + return out->cfg.rate; +} + +static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) +{ + DBG("rate %u", rate); + + return 0; +} + +static size_t out_get_buffer_size(const struct audio_stream *stream) +{ + struct sco_stream_out *out = (struct sco_stream_out *) stream; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + size_t size = audio_stream_out_frame_size(&out->stream) * + out->cfg.frame_num; +#else + size_t size = audio_stream_frame_size(&out->stream.common) * + out->cfg.frame_num; +#endif + + /* buffer size without resampling */ + if (out->cfg.rate == AUDIO_STREAM_SCO_RATE) + size = 576 * 2; + + DBG("buf size %zd", size); + + return size; +} + +static uint32_t out_get_channels(const struct audio_stream *stream) +{ + struct sco_stream_out *out = (struct sco_stream_out *) stream; + + DBG("channels num: %u", popcount(out->cfg.channels)); + + return out->cfg.channels; +} + +static audio_format_t out_get_format(const struct audio_stream *stream) +{ + struct sco_stream_out *out = (struct sco_stream_out *) stream; + + DBG("format: %u", out->cfg.format); + + return out->cfg.format; +} + +static int out_set_format(struct audio_stream *stream, audio_format_t format) +{ + DBG(""); + + return -ENOSYS; +} + +static int out_standby(struct audio_stream *stream) +{ + DBG(""); + + return 0; +} + +static int out_dump(const struct audio_stream *stream, int fd) +{ + DBG(""); + + return -ENOSYS; +} + +static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) +{ + DBG("%s", kvpairs); + + return 0; +} + +static char *out_get_parameters(const struct audio_stream *stream, + const char *keys) +{ + DBG(""); + + return strdup(""); +} + +static uint32_t out_get_latency(const struct audio_stream_out *stream) +{ + DBG(""); + + return 0; +} + +static int out_set_volume(struct audio_stream_out *stream, float left, + float right) +{ + DBG(""); + + return -ENOSYS; +} + +static int out_get_render_position(const struct audio_stream_out *stream, + uint32_t *dsp_frames) +{ + DBG(""); + + return -ENOSYS; +} + +static int out_add_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + + return -ENOSYS; +} + +static int out_remove_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + + return -ENOSYS; +} + +static int sco_open_output_stream_real(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out, + const char *address) +{ + struct sco_dev *adev = (struct sco_dev *) dev; + struct sco_stream_out *out; + int chan_num, ret; + size_t resample_size; + + DBG("config %p device flags 0x%02x", config, devices); + + if (sco_stream_out) { + DBG("stream_out already open"); + return -EIO; + } + + out = calloc(1, sizeof(struct sco_stream_out)); + if (!out) + return -ENOMEM; + + DBG("stream %p sco fd %d mtu %u", out, sco_fd, sco_mtu); + + out->stream.common.get_sample_rate = out_get_sample_rate; + out->stream.common.set_sample_rate = out_set_sample_rate; + out->stream.common.get_buffer_size = out_get_buffer_size; + out->stream.common.get_channels = out_get_channels; + out->stream.common.get_format = out_get_format; + out->stream.common.set_format = out_set_format; + out->stream.common.standby = out_standby; + out->stream.common.dump = out_dump; + out->stream.common.set_parameters = out_set_parameters; + out->stream.common.get_parameters = out_get_parameters; + out->stream.common.add_audio_effect = out_add_audio_effect; + out->stream.common.remove_audio_effect = out_remove_audio_effect; + out->stream.get_latency = out_get_latency; + out->stream.set_volume = out_set_volume; + out->stream.write = out_write; + out->stream.get_render_position = out_get_render_position; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + if (address) { + DBG("address %s", address); + + str2bt_bdaddr_t(address, &out->bd_addr); + } +#endif + + if (ipc_get_sco_fd(&out->bd_addr) != SCO_STATUS_SUCCESS) + DBG("SCO is not connected yet; get fd on write()"); + + if (config) { + DBG("config: rate %u chan mask %x format %d offload %p", + config->sample_rate, config->channel_mask, + config->format, &config->offload_info); + + out->cfg.format = config->format; + out->cfg.channels = config->channel_mask; + out->cfg.rate = config->sample_rate; + } else { + out->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT; + out->cfg.channels = AUDIO_CHANNEL_OUT_STEREO; + out->cfg.rate = AUDIO_STREAM_DEFAULT_RATE; + } + + out->cfg.frame_num = OUT_STREAM_FRAMES; + + out->downmix_buf = malloc(out_get_buffer_size(&out->stream.common)); + if (!out->downmix_buf) { + free(out); + return -ENOMEM; + } + + out->cache = malloc(sco_mtu); + if (!out->cache) { + free(out->downmix_buf); + free(out); + return -ENOMEM; + } + + if (out->cfg.rate == AUDIO_STREAM_SCO_RATE) + goto skip_resampler; + + /* Channel numbers for resampler */ + chan_num = 1; + + ret = create_resampler(out->cfg.rate, AUDIO_STREAM_SCO_RATE, chan_num, + RESAMPLER_QUALITY_DEFAULT, NULL, + &out->resampler); + if (ret) { + error("Failed to create resampler (%s)", strerror(-ret)); + goto failed; + } + + out->resample_frame_num = get_resample_frame_num(AUDIO_STREAM_SCO_RATE, + out->cfg.rate, + out->cfg.frame_num, 1); + + if (!out->resample_frame_num) { + error("frame num is too small to resample, discard it"); + goto failed; + } + + resample_size = sizeof(int16_t) * chan_num * out->resample_frame_num; + + out->resample_buf = malloc(resample_size); + if (!out->resample_buf) { + error("failed to allocate resample buffer for %u frames", + out->resample_frame_num); + goto failed; + } + + DBG("Resampler: input %d output %d chan %d frames %u size %zd", + out->cfg.rate, AUDIO_STREAM_SCO_RATE, chan_num, + out->resample_frame_num, resample_size); +skip_resampler: + *stream_out = &out->stream; + adev->out = out; + sco_stream_out = out; + + return 0; +failed: + if (out->resampler) + release_resampler(out->resampler); + + free(out->cache); + free(out->downmix_buf); + free(out); + *stream_out = NULL; + adev->out = NULL; + sco_stream_out = NULL; + + return ret; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int sco_open_output_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out, + const char *address) +{ + return sco_open_output_stream_real(dev, handle, devices, flags, + config, stream_out, address); +} +#else +static int sco_open_output_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out) +{ + return sco_open_output_stream_real(dev, handle, devices, flags, + config, stream_out, NULL); +} +#endif + +static void sco_close_output_stream(struct audio_hw_device *dev, + struct audio_stream_out *stream_out) +{ + struct sco_dev *sco_dev = (struct sco_dev *) dev; + struct sco_stream_out *out = (struct sco_stream_out *) stream_out; + + DBG("dev %p stream %p fd %d", dev, out, sco_fd); + + if (out->resampler) { + release_resampler(out->resampler); + free(out->resample_buf); + } + + free(out->cache); + free(out->downmix_buf); + free(out); + sco_dev->out = NULL; + + pthread_mutex_lock(&sco_mutex); + + sco_stream_out = NULL; + + if (!sco_stream_in) + sco_close_socket(); + + pthread_mutex_unlock(&sco_mutex); +} + +static int sco_set_parameters(struct audio_hw_device *dev, + const char *kvpairs) +{ + DBG("%s", kvpairs); + + return 0; +} + +static char *sco_get_parameters(const struct audio_hw_device *dev, + const char *keys) +{ + DBG(""); + + return strdup(""); +} + +static int sco_init_check(const struct audio_hw_device *dev) +{ + DBG(""); + + return 0; +} + +static int sco_set_voice_volume(struct audio_hw_device *dev, float volume) +{ + DBG("%f", volume); + + return 0; +} + +static int sco_set_master_volume(struct audio_hw_device *dev, float volume) +{ + DBG("%f", volume); + + return 0; +} + +static int sco_set_mode(struct audio_hw_device *dev, int mode) +{ + DBG(""); + + return -ENOSYS; +} + +static int sco_set_mic_mute(struct audio_hw_device *dev, bool state) +{ + DBG(""); + + return -ENOSYS; +} + +static int sco_get_mic_mute(const struct audio_hw_device *dev, bool *state) +{ + DBG(""); + + return -ENOSYS; +} + +static size_t sco_get_input_buffer_size(const struct audio_hw_device *dev, + const struct audio_config *config) +{ + DBG(""); + + return -ENOSYS; +} + +static uint32_t in_get_sample_rate(const struct audio_stream *stream) +{ + struct sco_stream_in *in = (struct sco_stream_in *) stream; + + DBG("rate %u", in->cfg.rate); + + return in->cfg.rate; +} + +static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) +{ + DBG("rate %u", rate); + + return 0; +} + +static size_t in_get_buffer_size(const struct audio_stream *stream) +{ + struct sco_stream_in *in = (struct sco_stream_in *) stream; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + size_t size = audio_stream_in_frame_size(&in->stream) * + in->cfg.frame_num; +#else + size_t size = audio_stream_frame_size(&in->stream.common) * + in->cfg.frame_num; +#endif + + /* buffer size without resampling */ + if (in->cfg.rate == AUDIO_STREAM_SCO_RATE) + size = 576; + + DBG("buf size %zd", size); + + return size; +} + +static uint32_t in_get_channels(const struct audio_stream *stream) +{ + struct sco_stream_in *in = (struct sco_stream_in *) stream; + + DBG("channels num: %u", popcount(in->cfg.channels)); + + return in->cfg.channels; +} + +static audio_format_t in_get_format(const struct audio_stream *stream) +{ + struct sco_stream_in *in = (struct sco_stream_in *) stream; + + DBG("format: %u", in->cfg.format); + + return in->cfg.format; +} + +static int in_set_format(struct audio_stream *stream, audio_format_t format) +{ + DBG(""); + + return -ENOSYS; +} + +static int in_standby(struct audio_stream *stream) +{ + DBG(""); + + return 0; +} + +static int in_dump(const struct audio_stream *stream, int fd) +{ + DBG(""); + + return -ENOSYS; +} + +static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) +{ + DBG("%s", kvpairs); + + return 0; +} + +static char *in_get_parameters(const struct audio_stream *stream, + const char *keys) +{ + DBG(""); + + return strdup(""); +} + +static int in_add_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + + return -ENOSYS; +} + +static int in_remove_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + DBG(""); + + return -ENOSYS; +} + +static int in_set_gain(struct audio_stream_in *stream, float gain) +{ + DBG(""); + + return -ENOSYS; +} + +static bool read_data(struct sco_stream_in *in, char *buffer, size_t bytes) +{ + struct pollfd pfd; + size_t len, read_bytes = 0; + + pfd.fd = sco_fd; + pfd.events = POLLIN | POLLHUP | POLLNVAL; + + while (bytes > read_bytes) { + int ret; + + /* poll for reading */ + if (poll(&pfd, 1, SOCKET_POLL_TIMEOUT_MS) == 0) { + DBG("timeout fd %d", sco_fd); + return false; + } + + if (pfd.revents & (POLLHUP | POLLNVAL)) { + error("error fd %d, events 0x%x", sco_fd, pfd.revents); + return false; + } + + len = bytes - read_bytes > sco_mtu ? sco_mtu : + bytes - read_bytes; + + ret = read(sco_fd, buffer + read_bytes, len); + if (ret > 0) { + read_bytes += ret; + DBG("read %d total %zd", ret, read_bytes); + continue; + } + + if (errno == EAGAIN) { + ret = errno; + warn("read failed (%d)", ret); + continue; + } + + if (errno != EINTR) { + ret = errno; + error("read failed (%d) fd %d bytes %zd", ret, sco_fd, + bytes); + return false; + } + } + + DBG("read %zd bytes", read_bytes); + + return true; +} + +static ssize_t in_read(struct audio_stream_in *stream, void *buffer, + size_t bytes) +{ + struct sco_stream_in *in = (struct sco_stream_in *) stream; + size_t frame_size, frame_num, input_frame_num; + void *read_buf = buffer; + size_t total = bytes; + int ret; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + frame_size = audio_stream_in_frame_size(&in->stream); +#else + frame_size = audio_stream_frame_size(&stream->common); +#endif + + if (!frame_size) + return -1; + + frame_num = bytes / frame_size; + input_frame_num = frame_num; + + DBG("Read from fd %d bytes %zu", sco_fd, bytes); + + if (ipc_get_sco_fd(&in->bd_addr) != SCO_STATUS_SUCCESS) + return -1; + + if (!in->resampler && in->cfg.rate != AUDIO_STREAM_SCO_RATE) { + error("Cannot find resampler"); + return -1; + } + + if (in->resampler) { + input_frame_num = get_resample_frame_num(AUDIO_STREAM_SCO_RATE, + in->cfg.rate, + frame_num, 0); + if (input_frame_num > in->resample_frame_num) { + DBG("resize input frames from %zd to %d", + input_frame_num, in->resample_frame_num); + input_frame_num = in->resample_frame_num; + } + + read_buf = in->resample_buf; + + total = input_frame_num * sizeof(int16_t) * 1; + } + + if(!read_data(in, read_buf, total)) + return -1; + + if (in->resampler) { + ret = in->resampler->resample_from_input(in->resampler, + in->resample_buf, + &input_frame_num, + (int16_t *) buffer, + &frame_num); + if (ret) { + error("Failed to resample frames: %zd input %zd (%s)", + frame_num, input_frame_num, + strerror(ret)); + return -1; + } + + DBG("resampler: remain %zd output %zd frames", input_frame_num, + frame_num); + } + + return bytes; +} + +static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) +{ + DBG(""); + + return -ENOSYS; +} + +static int sco_open_input_stream_real(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in, + audio_input_flags_t flags, + const char *address, + audio_source_t source) +{ + struct sco_dev *sco_dev = (struct sco_dev *) dev; + struct sco_stream_in *in; + int chan_num, ret; + size_t resample_size; + + DBG("config %p device flags 0x%02x", config, devices); + + if (sco_stream_in) { + DBG("stream_in already open"); + ret = -EIO; + goto failed2; + } + + in = calloc(1, sizeof(struct sco_stream_in)); + if (!in) + return -ENOMEM; + + DBG("stream %p sco fd %d mtu %u", in, sco_fd, sco_mtu); + + in->stream.common.get_sample_rate = in_get_sample_rate; + in->stream.common.set_sample_rate = in_set_sample_rate; + in->stream.common.get_buffer_size = in_get_buffer_size; + in->stream.common.get_channels = in_get_channels; + in->stream.common.get_format = in_get_format; + in->stream.common.set_format = in_set_format; + in->stream.common.standby = in_standby; + in->stream.common.dump = in_dump; + in->stream.common.set_parameters = in_set_parameters; + in->stream.common.get_parameters = in_get_parameters; + in->stream.common.add_audio_effect = in_add_audio_effect; + in->stream.common.remove_audio_effect = in_remove_audio_effect; + in->stream.set_gain = in_set_gain; + in->stream.read = in_read; + in->stream.get_input_frames_lost = in_get_input_frames_lost; + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + if (address) { + DBG("address %s", address); + + str2bt_bdaddr_t(address, &in->bd_addr); + } +#endif + + if (config) { + DBG("config: rate %u chan mask %x format %d offload %p", + config->sample_rate, config->channel_mask, + config->format, &config->offload_info); + + in->cfg.format = config->format; + in->cfg.channels = config->channel_mask; + in->cfg.rate = config->sample_rate; + } else { + in->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT; + in->cfg.channels = AUDIO_CHANNEL_OUT_MONO; + in->cfg.rate = AUDIO_STREAM_DEFAULT_RATE; + } + + in->cfg.frame_num = IN_STREAM_FRAMES; + + if (in->cfg.rate == AUDIO_STREAM_SCO_RATE) + goto skip_resampler; + + /* Channel numbers for resampler */ + chan_num = 1; + + ret = create_resampler(AUDIO_STREAM_SCO_RATE, in->cfg.rate, chan_num, + RESAMPLER_QUALITY_DEFAULT, NULL, + &in->resampler); + if (ret) { + error("Failed to create resampler (%s)", strerror(-ret)); + goto failed; + } + + in->resample_frame_num = get_resample_frame_num(AUDIO_STREAM_SCO_RATE, + in->cfg.rate, + in->cfg.frame_num, 0); + + resample_size = sizeof(int16_t) * chan_num * in->resample_frame_num; + + in->resample_buf = malloc(resample_size); + if (!in->resample_buf) { + error("failed to allocate resample buffer for %d frames", + in->resample_frame_num); + goto failed; + } + + DBG("Resampler: input %d output %d chan %d frames %u size %zd", + AUDIO_STREAM_SCO_RATE, in->cfg.rate, chan_num, + in->resample_frame_num, resample_size); +skip_resampler: + *stream_in = &in->stream; + sco_dev->in = in; + sco_stream_in = in; + + return 0; +failed: + if (in->resampler) + release_resampler(in->resampler); + free(in); +failed2: + *stream_in = NULL; + sco_dev->in = NULL; + sco_stream_in = NULL; + + return ret; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int sco_open_input_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in, + audio_input_flags_t flags, + const char *address, + audio_source_t source) +{ + return sco_open_input_stream_real(dev, handle, devices, config, + stream_in, flags, address, + source); +} +#else +static int sco_open_input_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in) +{ + return sco_open_input_stream_real(dev, handle, devices, config, + stream_in, 0, NULL, 0); +} +#endif + +static void sco_close_input_stream(struct audio_hw_device *dev, + struct audio_stream_in *stream_in) +{ + struct sco_dev *sco_dev = (struct sco_dev *) dev; + struct sco_stream_in *in = (struct sco_stream_in *) stream_in; + + DBG("dev %p stream %p fd %d", dev, in, sco_fd); + + if (in->resampler) { + release_resampler(in->resampler); + free(in->resample_buf); + } + + free(in); + sco_dev->in = NULL; + + pthread_mutex_lock(&sco_mutex); + + sco_stream_in = NULL; + + if (!sco_stream_out) + sco_close_socket(); + + pthread_mutex_unlock(&sco_mutex); +} + +static int sco_dump(const audio_hw_device_t *device, int fd) +{ + DBG(""); + + return 0; +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static int set_master_mute(struct audio_hw_device *dev, bool mute) +{ + DBG(""); + return -ENOSYS; +} + +static int get_master_mute(struct audio_hw_device *dev, bool *mute) +{ + DBG(""); + return -ENOSYS; +} + +static int create_audio_patch(struct audio_hw_device *dev, + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *handle) +{ + DBG(""); + return -ENOSYS; +} + +static int release_audio_patch(struct audio_hw_device *dev, + audio_patch_handle_t handle) +{ + DBG(""); + return -ENOSYS; +} + +static int get_audio_port(struct audio_hw_device *dev, struct audio_port *port) +{ + DBG(""); + return -ENOSYS; +} + +static int set_audio_port_config(struct audio_hw_device *dev, + const struct audio_port_config *config) +{ + DBG(""); + return -ENOSYS; +} +#endif + +static int sco_close(hw_device_t *device) +{ + DBG(""); + + free(device); + + return 0; +} + +static void *ipc_handler(void *data) +{ + bool done = false; + struct pollfd pfd; + int sk; + + DBG(""); + + while (!done) { + DBG("Waiting for connection ..."); + + sk = accept(listen_sk, NULL, NULL); + if (sk < 0) { + int err = errno; + + if (err == EINTR) + continue; + + if (err != ECONNABORTED && err != EINVAL) + error("sco: Failed to accept socket: %d (%s)", + err, strerror(err)); + + break; + } + + pthread_mutex_lock(&sk_mutex); + ipc_sk = sk; + pthread_mutex_unlock(&sk_mutex); + + DBG("SCO IPC: Connected"); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ipc_sk; + pfd.events = POLLHUP | POLLERR | POLLNVAL; + + /* Check if socket is still alive. Empty while loop.*/ + while (poll(&pfd, 1, -1) < 0 && errno == EINTR); + + info("SCO HAL: Socket closed"); + + pthread_mutex_lock(&sk_mutex); + close(ipc_sk); + ipc_sk = -1; + pthread_mutex_unlock(&sk_mutex); + } + + info("Closing SCO IPC thread"); + return NULL; +} + +static int sco_ipc_init(void) +{ + struct sockaddr_un addr; + int err; + int sk; + + DBG(""); + + sk = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (sk < 0) { + err = -errno; + error("sco: Failed to create socket: %d (%s)", -err, + strerror(-err)); + return err; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + memcpy(addr.sun_path, BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH)); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = -errno; + error("sco: Failed to bind socket: %d (%s)", -err, + strerror(-err)); + goto failed; + } + + if (listen(sk, 1) < 0) { + err = -errno; + error("sco: Failed to listen on the socket: %d (%s)", -err, + strerror(-err)); + goto failed; + } + + listen_sk = sk; + + err = pthread_create(&ipc_th, NULL, ipc_handler, NULL); + if (err) { + err = -err; + ipc_th = 0; + error("sco: Failed to start IPC thread: %d (%s)", + -err, strerror(-err)); + goto failed; + } + + return 0; + +failed: + close(sk); + return err; +} + +static int sco_open(const hw_module_t *module, const char *name, + hw_device_t **device) +{ + struct sco_dev *dev; + int err; + + DBG(""); + + if (strcmp(name, AUDIO_HARDWARE_INTERFACE)) { + error("SCO: interface %s not matching [%s]", name, + AUDIO_HARDWARE_INTERFACE); + return -EINVAL; + } + + err = sco_ipc_init(); + if (err < 0) + return err; + + dev = calloc(1, sizeof(struct sco_dev)); + if (!dev) + return -ENOMEM; + + dev->dev.common.tag = HARDWARE_DEVICE_TAG; + dev->dev.common.version = AUDIO_DEVICE_API_VERSION_CURRENT; + dev->dev.common.module = (struct hw_module_t *) module; + dev->dev.common.close = sco_close; + + dev->dev.init_check = sco_init_check; + dev->dev.set_voice_volume = sco_set_voice_volume; + dev->dev.set_master_volume = sco_set_master_volume; + dev->dev.set_mode = sco_set_mode; + dev->dev.set_mic_mute = sco_set_mic_mute; + dev->dev.get_mic_mute = sco_get_mic_mute; + dev->dev.set_parameters = sco_set_parameters; + dev->dev.get_parameters = sco_get_parameters; + dev->dev.get_input_buffer_size = sco_get_input_buffer_size; + dev->dev.open_output_stream = sco_open_output_stream; + dev->dev.close_output_stream = sco_close_output_stream; + dev->dev.open_input_stream = sco_open_input_stream; + dev->dev.close_input_stream = sco_close_input_stream; + dev->dev.dump = sco_dump; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + dev->dev.set_master_mute = set_master_mute; + dev->dev.get_master_mute = get_master_mute; + dev->dev.create_audio_patch = create_audio_patch; + dev->dev.release_audio_patch = release_audio_patch; + dev->dev.get_audio_port = get_audio_port; + dev->dev.set_audio_port_config = set_audio_port_config; +#endif + + *device = &dev->dev.common; + + return 0; +} + +static struct hw_module_methods_t hal_module_methods = { + .open = sco_open, +}; + +struct audio_module HAL_MODULE_INFO_SYM = { + .common = { + .tag = HARDWARE_MODULE_TAG, + .version_major = 1, + .version_minor = 0, + .id = AUDIO_HARDWARE_MODULE_ID, + .name = "SCO Audio HW HAL", + .author = "Intel Corporation", + .methods = &hal_module_methods, + }, +};
diff --git a/android/hal-socket.c b/android/hal-socket.c new file mode 100644 index 0000000..cfd50d1 --- /dev/null +++ b/android/hal-socket.c
@@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "hal-ipc.h" +#include "hal-log.h" +#include "hal-msg.h" +#include "hal-utils.h" +#include "hal.h" + +static bt_status_t socket_listen(btsock_type_t type, const char *service_name, + const uint8_t *uuid, int chan, + int *sock, int flags) +{ + struct hal_cmd_socket_listen cmd; + + if (!sock) + return BT_STATUS_PARM_INVALID; + + DBG("uuid %s chan %d sock %p type %d service_name %s flags 0x%02x", + btuuid2str(uuid), chan, sock, type, service_name, flags); + + memset(&cmd, 0, sizeof(cmd)); + + /* type match IPC type */ + cmd.type = type; + cmd.flags = flags; + cmd.channel = chan; + + if (uuid) + memcpy(cmd.uuid, uuid, sizeof(cmd.uuid)); + + if (service_name) + memcpy(cmd.name, service_name, strlen(service_name)); + + return hal_ipc_cmd(HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_LISTEN, + sizeof(cmd), &cmd, NULL, NULL, sock); +} + +static bt_status_t socket_connect(const bt_bdaddr_t *bdaddr, btsock_type_t type, + const uint8_t *uuid, int chan, + int *sock, int flags) +{ + struct hal_cmd_socket_connect cmd; + + if (!sock) + return BT_STATUS_PARM_INVALID; + + DBG("bdaddr %s uuid %s chan %d sock %p type %d flags 0x%02x", + bdaddr2str(bdaddr), btuuid2str(uuid), chan, sock, type, flags); + + memset(&cmd, 0, sizeof(cmd)); + + /* type match IPC type */ + cmd.type = type; + cmd.flags = flags; + cmd.channel = chan; + + if (uuid) + memcpy(cmd.uuid, uuid, sizeof(cmd.uuid)); + + if (bdaddr) + memcpy(cmd.bdaddr, bdaddr, sizeof(cmd.bdaddr)); + + return hal_ipc_cmd(HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_CONNECT, + sizeof(cmd), &cmd, NULL, NULL, sock); +} + +static btsock_interface_t socket_if = { + sizeof(socket_if), + socket_listen, + socket_connect +}; + +btsock_interface_t *bt_get_socket_interface(void) +{ + return &socket_if; +}
diff --git a/android/hal-utils.c b/android/hal-utils.c new file mode 100644 index 0000000..e45f6e4 --- /dev/null +++ b/android/hal-utils.c
@@ -0,0 +1,418 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> + +#include <cutils/properties.h> + +#include "hal.h" +#include "hal-utils.h" + +/* + * converts uuid to string + * buf should be at least 39 bytes + * + * returns string representation of uuid + */ +const char *bt_uuid_t2str(const uint8_t *uuid, char *buf) +{ + int shift = 0; + unsigned int i; + int is_bt; + + if (!uuid) + return strcpy(buf, "NULL"); + + is_bt = !memcmp(&uuid[4], &BT_BASE_UUID[4], HAL_UUID_LEN - 4); + + for (i = 0; i < HAL_UUID_LEN; i++) { + if (i == 4 && is_bt) + break; + + if (i == 4 || i == 6 || i == 8 || i == 10) { + buf[i * 2 + shift] = '-'; + shift++; + } + sprintf(buf + i * 2 + shift, "%02x", uuid[i]); + } + + return buf; +} + +const char *btuuid2str(const uint8_t *uuid) +{ + static char buf[MAX_UUID_STR_LEN]; + + return bt_uuid_t2str(uuid, buf); +} + +INTMAP(bt_status_t, -1, "(unknown)") + DELEMENT(BT_STATUS_SUCCESS), + DELEMENT(BT_STATUS_FAIL), + DELEMENT(BT_STATUS_NOT_READY), + DELEMENT(BT_STATUS_NOMEM), + DELEMENT(BT_STATUS_BUSY), + DELEMENT(BT_STATUS_DONE), + DELEMENT(BT_STATUS_UNSUPPORTED), + DELEMENT(BT_STATUS_PARM_INVALID), + DELEMENT(BT_STATUS_UNHANDLED), + DELEMENT(BT_STATUS_AUTH_FAILURE), + DELEMENT(BT_STATUS_RMT_DEV_DOWN), +ENDMAP + +INTMAP(bt_state_t, -1, "(unknown)") + DELEMENT(BT_STATE_OFF), + DELEMENT(BT_STATE_ON), +ENDMAP + +INTMAP(bt_device_type_t, -1, "(unknown)") + DELEMENT(BT_DEVICE_DEVTYPE_BREDR), + DELEMENT(BT_DEVICE_DEVTYPE_BLE), + DELEMENT(BT_DEVICE_DEVTYPE_DUAL), +ENDMAP + +INTMAP(bt_scan_mode_t, -1, "(unknown)") + DELEMENT(BT_SCAN_MODE_NONE), + DELEMENT(BT_SCAN_MODE_CONNECTABLE), + DELEMENT(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE), +ENDMAP + +INTMAP(bt_discovery_state_t, -1, "(unknown)") + DELEMENT(BT_DISCOVERY_STOPPED), + DELEMENT(BT_DISCOVERY_STARTED), +ENDMAP + +INTMAP(bt_acl_state_t, -1, "(unknown)") + DELEMENT(BT_ACL_STATE_CONNECTED), + DELEMENT(BT_ACL_STATE_DISCONNECTED), +ENDMAP + +INTMAP(bt_bond_state_t, -1, "(unknown)") + DELEMENT(BT_BOND_STATE_NONE), + DELEMENT(BT_BOND_STATE_BONDING), + DELEMENT(BT_BOND_STATE_BONDED), +ENDMAP + +INTMAP(bt_ssp_variant_t, -1, "(unknown)") + DELEMENT(BT_SSP_VARIANT_PASSKEY_CONFIRMATION), + DELEMENT(BT_SSP_VARIANT_PASSKEY_ENTRY), + DELEMENT(BT_SSP_VARIANT_CONSENT), + DELEMENT(BT_SSP_VARIANT_PASSKEY_NOTIFICATION), +ENDMAP + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +INTMAP(bt_property_type_t, -1, "(unknown)") + DELEMENT(BT_PROPERTY_BDNAME), + DELEMENT(BT_PROPERTY_BDADDR), + DELEMENT(BT_PROPERTY_UUIDS), + DELEMENT(BT_PROPERTY_CLASS_OF_DEVICE), + DELEMENT(BT_PROPERTY_TYPE_OF_DEVICE), + DELEMENT(BT_PROPERTY_SERVICE_RECORD), + DELEMENT(BT_PROPERTY_ADAPTER_SCAN_MODE), + DELEMENT(BT_PROPERTY_ADAPTER_BONDED_DEVICES), + DELEMENT(BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT), + DELEMENT(BT_PROPERTY_REMOTE_FRIENDLY_NAME), + DELEMENT(BT_PROPERTY_REMOTE_RSSI), + DELEMENT(BT_PROPERTY_REMOTE_VERSION_INFO), + DELEMENT(BT_PROPERTY_LOCAL_LE_FEATURES), + DELEMENT(BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP), +ENDMAP +#else +INTMAP(bt_property_type_t, -1, "(unknown)") + DELEMENT(BT_PROPERTY_BDNAME), + DELEMENT(BT_PROPERTY_BDADDR), + DELEMENT(BT_PROPERTY_UUIDS), + DELEMENT(BT_PROPERTY_CLASS_OF_DEVICE), + DELEMENT(BT_PROPERTY_TYPE_OF_DEVICE), + DELEMENT(BT_PROPERTY_SERVICE_RECORD), + DELEMENT(BT_PROPERTY_ADAPTER_SCAN_MODE), + DELEMENT(BT_PROPERTY_ADAPTER_BONDED_DEVICES), + DELEMENT(BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT), + DELEMENT(BT_PROPERTY_REMOTE_FRIENDLY_NAME), + DELEMENT(BT_PROPERTY_REMOTE_RSSI), + DELEMENT(BT_PROPERTY_REMOTE_VERSION_INFO), + DELEMENT(BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP), +ENDMAP +#endif + +INTMAP(bt_cb_thread_evt, -1, "(unknown)") + DELEMENT(ASSOCIATE_JVM), + DELEMENT(DISASSOCIATE_JVM), +ENDMAP + +/* Find first index of given value in table m */ +int int2str_findint(int v, const struct int2str m[]) +{ + int i; + + for (i = 0; m[i].str; ++i) { + if (m[i].val == v) + return i; + } + return -1; +} + +/* Find first index of given string in table m */ +int int2str_findstr(const char *str, const struct int2str m[]) +{ + int i; + + for (i = 0; m[i].str; ++i) { + if (strcmp(m[i].str, str) == 0) + return i; + } + return -1; +} + +/* + * convert bd_addr to string + * buf must be at least 18 char long + * + * returns buf + */ +const char *bt_bdaddr_t2str(const bt_bdaddr_t *bd_addr, char *buf) +{ + const uint8_t *p; + + if (!bd_addr) + return strcpy(buf, "NULL"); + + p = bd_addr->address; + + snprintf(buf, MAX_ADDR_STR_LEN, "%02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + + return buf; +} + +/* converts string to bt_bdaddr_t */ +void str2bt_bdaddr_t(const char *str, bt_bdaddr_t *bd_addr) +{ + uint8_t *p = bd_addr->address; + + sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &p[0], &p[1], &p[2], &p[3], &p[4], &p[5]); +} + +/* converts string to uuid */ +void str2bt_uuid_t(const char *str, bt_uuid_t *uuid) +{ + int i = 0; + + memcpy(uuid, BT_BASE_UUID, sizeof(bt_uuid_t)); + + while (*str && i < (int) sizeof(bt_uuid_t)) { + while (*str == '-') + str++; + + if (sscanf(str, "%02hhx", &uuid->uu[i]) != 1) + break; + + i++; + str += 2; + } +} + +const char *enum_defines(void *v, int i) +{ + const struct int2str *m = v; + + return m[i].str != NULL ? m[i].str : NULL; +} + +const char *enum_strings(void *v, int i) +{ + const char **m = v; + + return m[i] != NULL ? m[i] : NULL; +} + +const char *enum_one_string(void *v, int i) +{ + const char *m = v; + + return (i == 0) && (m[0] != 0) ? m : NULL; +} + +const char *bdaddr2str(const bt_bdaddr_t *bd_addr) +{ + static char buf[MAX_ADDR_STR_LEN]; + + return bt_bdaddr_t2str(bd_addr, buf); +} + +static void bonded_devices2string(char *str, void *prop, int prop_len) +{ + int count = prop_len / sizeof(bt_bdaddr_t); + bt_bdaddr_t *addr = prop; + + strcat(str, "{"); + + while (count--) { + strcat(str, bdaddr2str(addr)); + if (count) + strcat(str, ", "); + addr++; + } + + strcat(str, "}"); +} + +static void uuids2string(char *str, void *prop, int prop_len) +{ + int count = prop_len / sizeof(bt_uuid_t); + bt_uuid_t *uuid = prop; + + strcat(str, "{"); + + while (count--) { + strcat(str, btuuid2str(uuid->uu)); + if (count) + strcat(str, ", "); + uuid++; + } + + strcat(str, "}"); +} + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +static void local_le_feat2string(char *str, const bt_local_le_features_t *f) +{ + uint16_t scan_num; + + str += sprintf(str, "{\n"); + + str += sprintf(str, "Privacy supported: %s,\n", + f->local_privacy_enabled ? "TRUE" : "FALSE"); + + str += sprintf(str, "Num of advertising instances: %u,\n", + f->max_adv_instance); + + str += sprintf(str, "PRA offloading support: %s,\n", + f->rpa_offload_supported ? "TRUE" : "FALSE"); + + str += sprintf(str, "Num of offloaded IRKs: %u,\n", + f->max_irk_list_size); + + str += sprintf(str, "Num of offloaded scan filters: %u,\n", + f->max_adv_filter_supported); + + scan_num = (f->scan_result_storage_size_hibyte << 8) + + f->scan_result_storage_size_lobyte; + + str += sprintf(str, "Num of offloaded scan results: %u,\n", scan_num); + + str += sprintf(str, "Activity & energy report support: %s\n", + f->activity_energy_info_supported ? "TRUE" : "FALSE"); + + sprintf(str, "}"); +} +#endif + +const char *btproperty2str(const bt_property_t *property) +{ + bt_service_record_t *rec; + static char buf[4096]; + char *p; + + p = buf + sprintf(buf, "type=%s len=%d val=", + bt_property_type_t2str(property->type), + property->len); + + switch (property->type) { + case BT_PROPERTY_BDNAME: + case BT_PROPERTY_REMOTE_FRIENDLY_NAME: + snprintf(p, property->len + 1, "%s", + ((bt_bdname_t *) property->val)->name); + break; + case BT_PROPERTY_BDADDR: + sprintf(p, "%s", bdaddr2str((bt_bdaddr_t *) property->val)); + break; + case BT_PROPERTY_CLASS_OF_DEVICE: + sprintf(p, "%06x", *((int *) property->val)); + break; + case BT_PROPERTY_TYPE_OF_DEVICE: + sprintf(p, "%s", bt_device_type_t2str( + *((bt_device_type_t *) property->val))); + break; + case BT_PROPERTY_REMOTE_RSSI: + sprintf(p, "%d", *((char *) property->val)); + break; + case BT_PROPERTY_ADAPTER_SCAN_MODE: + sprintf(p, "%s", + bt_scan_mode_t2str(*((bt_scan_mode_t *) property->val))); + break; + case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT: + sprintf(p, "%d", *((int *) property->val)); + break; + case BT_PROPERTY_ADAPTER_BONDED_DEVICES: + bonded_devices2string(p, property->val, property->len); + break; + case BT_PROPERTY_UUIDS: + uuids2string(p, property->val, property->len); + break; + case BT_PROPERTY_SERVICE_RECORD: + rec = property->val; + sprintf(p, "{%s, %d, %s}", btuuid2str(rec->uuid.uu), + rec->channel, rec->name); + break; +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + case BT_PROPERTY_LOCAL_LE_FEATURES: + local_le_feat2string(p, property->val); + break; +#endif + case BT_PROPERTY_REMOTE_VERSION_INFO: + case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP: + default: + sprintf(p, "%p", property->val); + break; + } + + return buf; +} + +#define PROP_PREFIX "persist.sys.bluetooth." +#define PROP_PREFIX_RO "ro.bluetooth." + +int get_config(const char *config_key, char *value, const char *fallback) +{ + char key[PROPERTY_KEY_MAX]; + int ret; + + if (strlen(config_key) + sizeof(PROP_PREFIX) > sizeof(key)) + return 0; + + snprintf(key, sizeof(key), PROP_PREFIX"%s", config_key); + + ret = property_get(key, value, ""); + if (ret > 0) + return ret; + + snprintf(key, sizeof(key), PROP_PREFIX_RO"%s", config_key); + + ret = property_get(key, value, ""); + if (ret > 0) + return ret; + + if (!fallback) + return 0; + + return property_get(fallback, value, ""); +}
diff --git a/android/hal-utils.h b/android/hal-utils.h new file mode 100644 index 0000000..9c59948 --- /dev/null +++ b/android/hal-utils.h
@@ -0,0 +1,152 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <endian.h> + +#include <hardware/bluetooth.h> + +#define MAX_UUID_STR_LEN 37 +#define HAL_UUID_LEN 16 +#define MAX_ADDR_STR_LEN 18 + +static const char BT_BASE_UUID[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb +}; + +const char *bt_uuid_t2str(const uint8_t *uuid, char *buf); +const char *btuuid2str(const uint8_t *uuid); +const char *bt_bdaddr_t2str(const bt_bdaddr_t *bd_addr, char *buf); +void str2bt_bdaddr_t(const char *str, bt_bdaddr_t *bd_addr); +void str2bt_uuid_t(const char *str, bt_uuid_t *uuid); +const char *btproperty2str(const bt_property_t *property); +const char *bdaddr2str(const bt_bdaddr_t *bd_addr); + +int get_config(const char *config_key, char *value, const char *fallback); + +/* + * Begin mapping section + * + * There are some mappings between integer values (enums) and strings + * to be presented to user. To make it easier to convert between those two + * set of macros is given. It is specially useful when we want to have + * strings that match constants from header files like: + * BT_STATUS_SUCCESS (0) and corresponding "BT_STATUS_SUCCESS" + * Example of usage: + * + * INTMAP(int, -1, "invalid") + * DELEMENT(BT_STATUS_SUCCESS) + * DELEMENT(BT_STATUS_FAIL) + * MELEMENT(123, "Some strange value") + * ENDMAP + * + * Just by doing this we have mapping table plus two functions: + * int str2int(const char *str); + * const char *int2str(int v); + * + * second argument to INTMAP specifies value to be returned from + * str2int function when there is not mapping for such number + * third argument specifies default value to be returned from int2str + * + * If same mapping is to be used in several source files put + * INTMAP in c file and DECINTMAP in h file. + * + * For mappings that are to be used in single file only + * use SINTMAP which will create the same but everything will be marked + * as static. + */ + +struct int2str { + int val; /* int value */ + const char *str; /* corresponding string */ +}; + +int int2str_findint(int v, const struct int2str m[]); +int int2str_findstr(const char *str, const struct int2str m[]); +const char *enum_defines(void *v, int i); +const char *enum_strings(void *v, int i); +const char *enum_one_string(void *v, int i); + +#define TYPE_ENUM(type) ((void *) &__##type##2str[0]) +#define DECINTMAP(type) \ +extern struct int2str __##type##2str[]; \ +const char *type##2##str(type v); \ +type str##2##type(const char *str); \ + +#define INTMAP(type, deft, defs) \ +const char *type##2##str(type v) \ +{ \ + int i = int2str_findint((int) v, __##type##2str); \ + return (i < 0) ? defs : __##type##2str[i].str; \ +} \ +type str##2##type(const char *str) \ +{ \ + int i = int2str_findstr(str, __##type##2str); \ + return (i < 0) ? (type) deft : (type) (__##type##2str[i].val); \ +} \ +struct int2str __##type##2str[] = { + +#define SINTMAP(type, deft, defs) \ +static struct int2str __##type##2str[]; \ +static inline const char *type##2##str(type v) \ +{ \ + int i = int2str_findint((int) v, __##type##2str); \ + return (i < 0) ? defs : __##type##2str[i].str; \ +} \ +static inline type str##2##type(const char *str) \ +{ \ + int i = int2str_findstr(str, __##type##2str); \ + return (i < 0) ? (type) deft : (type) (__##type##2str[i].val); \ +} \ +static struct int2str __##type##2str[] = { + +#define ENDMAP {0, NULL} }; + +/* use this to generate string from header file constant */ +#define MELEMENT(v, s) {v, s} +/* use this to have arbitrary mapping from int to string */ +#define DELEMENT(s) {s, #s} +/* End of mapping section */ + +DECINTMAP(bt_status_t); +DECINTMAP(bt_state_t); +DECINTMAP(bt_device_type_t); +DECINTMAP(bt_scan_mode_t); +DECINTMAP(bt_discovery_state_t); +DECINTMAP(bt_acl_state_t); +DECINTMAP(bt_bond_state_t); +DECINTMAP(bt_ssp_variant_t); +DECINTMAP(bt_property_type_t); +DECINTMAP(bt_cb_thread_evt); + +static inline uint16_t get_le16(const void *src) +{ + const struct __attribute__((packed)) { + uint16_t le16; + } *p = src; + + return le16toh(p->le16); +} + +static inline void put_le16(uint16_t val, void *dst) +{ + struct __attribute__((packed)) { + uint16_t le16; + } *p = dst; + + p->le16 = htole16(val); +}
diff --git a/android/hal.h b/android/hal.h new file mode 100644 index 0000000..709c197 --- /dev/null +++ b/android/hal.h
@@ -0,0 +1,54 @@ +/* + * Copyright (C) 2013 Intel Corporation + * + * 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. + * + */ + +#include <hardware/bluetooth.h> +#include <hardware/bt_sock.h> +#include <hardware/bt_hh.h> +#include <hardware/bt_pan.h> +#include <hardware/bt_av.h> +#include <hardware/bt_rc.h> +#include <hardware/bt_hf.h> +#include <hardware/bt_gatt.h> +#include <hardware/bt_gatt_client.h> +#include <hardware/bt_gatt_server.h> +#include <hardware/bt_hl.h> + +#define PLATFORM_VER(a, b, c) ((a << 16) | ( b << 8) | (c)) + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +#include <hardware/bt_hf_client.h> +#include <hardware/bt_mce.h> +#endif + +btsock_interface_t *bt_get_socket_interface(void); +bthh_interface_t *bt_get_hidhost_interface(void); +btpan_interface_t *bt_get_pan_interface(void); +btav_interface_t *bt_get_a2dp_interface(void); +btrc_interface_t *bt_get_avrcp_interface(void); +bthf_interface_t *bt_get_handsfree_interface(void); +btgatt_interface_t *bt_get_gatt_interface(void); +bthl_interface_t *bt_get_health_interface(void); + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +btrc_ctrl_interface_t *bt_get_avrcp_ctrl_interface(void); +bthf_client_interface_t *bt_get_hf_client_interface(void); +btmce_interface_t *bt_get_map_client_interface(void); +btav_interface_t *bt_get_a2dp_sink_interface(void); +#endif + +void bt_thread_associate(void); +void bt_thread_disassociate(void);
diff --git a/android/handsfree-client.c b/android/handsfree-client.c new file mode 100644 index 0000000..65659b8 --- /dev/null +++ b/android/handsfree-client.c
@@ -0,0 +1,2204 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "src/sdp-client.h" +#include "src/shared/hfp.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "btio/btio.h" +#include "ipc.h" +#include "ipc-common.h" +#include "src/log.h" +#include "utils.h" + +#include "bluetooth.h" +#include "hal-msg.h" +#include "handsfree-client.h" +#include "sco.h" + +#define HFP_HF_CHANNEL 7 + +#define HFP_HF_FEAT_ECNR 0x00000001 +#define HFP_HF_FEAT_3WAY 0x00000002 +#define HFP_HF_FEAT_CLI 0x00000004 +#define HFP_HF_FEAT_VR 0x00000008 +#define HFP_HF_FEAT_RVC 0x00000010 +#define HFP_HF_FEAT_ECS 0x00000020 +#define HFP_HF_FEAT_ECC 0x00000040 +#define HFP_HF_FEAT_CODEC 0x00000080 +#define HFP_HF_FEAT_HF_IND 0x00000100 +#define HFP_HF_FEAT_ESCO_S4_T2 0x00000200 + +#define HFP_AG_FEAT_3WAY 0x00000001 +#define HFP_AG_FEAT_ECNR 0x00000002 +#define HFP_AG_FEAT_VR 0x00000004 +#define HFP_AG_FEAT_INBAND 0x00000008 +#define HFP_AG_FEAT_VTAG 0x00000010 +#define HFP_AG_FEAT_REJ_CALL 0x00000020 +#define HFP_AG_FEAT_ECS 0x00000040 +#define HFP_AG_FEAT_ECC 0x00000080 +#define HFP_AG_FEAT_EXT_ERR 0x00000100 +#define HFP_AG_FEAT_CODEC 0x00000200 + +#define HFP_HF_FEATURES (HFP_HF_FEAT_ECNR | HFP_HF_FEAT_3WAY |\ + HFP_HF_FEAT_CLI | HFP_HF_FEAT_VR |\ + HFP_HF_FEAT_RVC | HFP_HF_FEAT_ECS |\ + HFP_HF_FEAT_ECC) + +#define CVSD_OFFSET 0 +#define MSBC_OFFSET 1 +#define CODECS_COUNT (MSBC_OFFSET + 1) + +#define CODEC_ID_CVSD 0x01 +#define CODEC_ID_MSBC 0x02 + +#define MAX_NUMBER_LEN 33 +#define MAX_OPERATOR_NAME_LEN 17 + +enum hfp_indicator { + HFP_INDICATOR_SERVICE = 0, + HFP_INDICATOR_CALL, + HFP_INDICATOR_CALLSETUP, + HFP_INDICATOR_CALLHELD, + HFP_INDICATOR_SIGNAL, + HFP_INDICATOR_ROAM, + HFP_INDICATOR_BATTCHG, + HFP_INDICATOR_LAST +}; + +typedef void (*ciev_func_t)(uint8_t val); + +struct indicator { + uint8_t index; + uint32_t min; + uint32_t max; + uint32_t val; + ciev_func_t cb; +}; + +struct hfp_codec { + uint8_t type; + bool local_supported; + bool remote_supported; +}; + +struct device { + bdaddr_t bdaddr; + struct hfp_hf *hf; + uint8_t state; + uint8_t audio_state; + + uint8_t negotiated_codec; + uint32_t features; + struct hfp_codec codecs[2]; + + struct indicator ag_ind[HFP_INDICATOR_LAST]; + + uint32_t chld_features; +}; + +static const struct hfp_codec codecs_defaults[] = { + { CODEC_ID_CVSD, true, false}, + { CODEC_ID_MSBC, false, false}, +}; + +static bdaddr_t adapter_addr; + +static struct ipc *hal_ipc = NULL; + +static uint32_t hfp_hf_features = 0; +static uint32_t hfp_hf_record_id = 0; +static struct queue *devices = NULL; +static GIOChannel *hfp_hf_server = NULL; + +static struct bt_sco *sco = NULL; + +static struct device *find_default_device(void) +{ + return queue_peek_head(devices); +} + +static bool match_by_bdaddr(const void *data, const void *user_data) +{ + const bdaddr_t *addr1 = data; + const bdaddr_t *addr2 = user_data; + + return !bacmp(addr1, addr2); +} + +static struct device *find_device(const bdaddr_t *addr) +{ + return queue_find(devices, match_by_bdaddr, addr); +} + +static void init_codecs(struct device *dev) +{ + memcpy(&dev->codecs, codecs_defaults, sizeof(dev->codecs)); + + if (hfp_hf_features & HFP_HF_FEAT_CODEC) + dev->codecs[MSBC_OFFSET].local_supported = true; +} + +static struct device *device_create(const bdaddr_t *bdaddr) +{ + struct device *dev; + + dev = new0(struct device, 1); + + bacpy(&dev->bdaddr, bdaddr); + dev->state = HAL_HF_CLIENT_CONN_STATE_DISCONNECTED; + dev->audio_state = HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED; + + init_codecs(dev); + + queue_push_tail(devices, dev); + + return dev; +} + +static struct device *get_device(const bdaddr_t *addr) +{ + struct device *dev; + + dev = find_device(addr); + if (dev) + return dev; + + /* We do support only one device as for now */ + if (queue_isempty(devices)) + return device_create(addr); + + return NULL; +} + +static void device_set_state(struct device *dev, uint8_t state) +{ + struct hal_ev_hf_client_conn_state ev; + char address[18]; + + if (dev->state == state) + return; + + memset(&ev, 0, sizeof(ev)); + + dev->state = state; + + ba2str(&dev->bdaddr, address); + DBG("device %s state %u", address, state); + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + ev.state = state; + + ev.chld_feat = dev->chld_features; + ev.peer_feat = dev->features; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_CONN_STATE, sizeof(ev), &ev); +} + +static void device_destroy(struct device *dev) +{ + device_set_state(dev, HAL_HF_CLIENT_CONN_STATE_DISCONNECTED); + queue_remove(devices, dev); + + if (dev->hf) + hfp_hf_unref(dev->hf); + + free(dev); +} + +static void handle_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_disconnect *cmd = buf; + struct device *dev; + uint32_t status; + bdaddr_t bdaddr; + char addr[18]; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &bdaddr); + + ba2str(&bdaddr, addr); + DBG("Disconnect %s", addr); + + dev = get_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->state == HAL_HF_CLIENT_CONN_STATE_DISCONNECTED) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->state == HAL_HF_CLIENT_CONN_STATE_DISCONNECTING) { + status = HAL_STATUS_SUCCESS; + goto done; + } + + if (dev->state == HAL_HF_CLIENT_CONN_STATE_CONNECTING) { + device_destroy(dev); + status = HAL_STATUS_SUCCESS; + goto done; + } + + status = hfp_hf_disconnect(dev->hf) ? HAL_STATUS_SUCCESS : + HAL_STATUS_FAILED; + + if (status) + device_set_state(dev, HAL_HF_CLIENT_CONN_STATE_DISCONNECTING); + +done: + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT, status); +} + +static void set_audio_state(struct device *dev, uint8_t state) +{ + struct hal_ev_hf_client_audio_state ev; + char address[18]; + + if (dev->audio_state == state) + return; + + dev->audio_state = state; + + ba2str(&dev->bdaddr, address); + DBG("device %s audio state %u", address, state); + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_AUDIO_STATE, sizeof(ev), &ev); +} + +static void bcc_cb(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + if (result != HFP_RESULT_OK) + set_audio_state(dev, HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED); +} + +static bool codec_negotiation_supported(struct device *dev) +{ + return (dev->features & HFP_AG_FEAT_CODEC) && + (hfp_hf_features & HFP_HF_FEAT_CODEC); +} + +static bool connect_sco(struct device *dev) +{ + if (codec_negotiation_supported(dev)) + return hfp_hf_send_command(dev->hf, bcc_cb, dev, + "AT+BCC"); + + return bt_sco_connect(sco, &dev->bdaddr, BT_VOICE_CVSD_16BIT); +} + +static void handle_connect_audio(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_connect_audio *cmd = (void *) buf; + struct device *dev; + uint8_t status; + bdaddr_t bdaddr; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev || dev->state != HAL_HF_CLIENT_CONN_STATE_SLC_CONNECTED || + dev->audio_state != HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED) { + error("hf-client: Cannot create SCO, check SLC or audio state"); + status = HAL_STATUS_FAILED; + goto done; + } + + if (connect_sco(dev)) { + status = HAL_STATUS_SUCCESS; + set_audio_state(dev, HAL_HF_CLIENT_AUDIO_STATE_CONNECTING); + } else { + status = HAL_STATUS_FAILED; + } + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT_AUDIO, status); +} + +static void handle_disconnect_audio(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_disconnect_audio *cmd = (void *) buf; + struct device *dev; + uint8_t status; + bdaddr_t bdaddr; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev || + dev->audio_state == HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED) { + error("hf-client: Device not found or audio not connected"); + status = HAL_STATUS_FAILED; + goto done; + } + + bt_sco_disconnect(sco); + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT_AUDIO, status); +} + +static void cmd_complete_cb(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct hal_ev_hf_client_command_complete ev; + + DBG(""); + memset(&ev, 0, sizeof(ev)); + + switch (result) { + case HFP_RESULT_OK: + ev.type = HAL_HF_CLIENT_CMD_COMP_OK; + break; + case HFP_RESULT_NO_CARRIER: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR_NO_CARRIER; + break; + case HFP_RESULT_ERROR: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR; + break; + case HFP_RESULT_BUSY: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR_BUSY; + break; + case HFP_RESULT_NO_ANSWER: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR_NO_ANSWER; + break; + case HFP_RESULT_DELAYED: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR_DELAYED; + break; + case HFP_RESULT_BLACKLISTED: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR_BACKLISTED; + break; + case HFP_RESULT_CME_ERROR: + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR_CME; + ev.cme = cme_err; + break; + case HFP_RESULT_CONNECT: + case HFP_RESULT_RING: + case HFP_RESULT_NO_DIALTONE: + default: + error("hf-client: Unknown error code %d", result); + ev.type = HAL_HF_CLIENT_CMD_COMP_ERR; + break; + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_CLIENT_COMMAND_COMPLETE, sizeof(ev), &ev); +} + +static void handle_start_vr(const void *buf, uint16_t len) +{ + struct device *dev; + uint8_t status; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+BVRA=1")) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_START_VR, status); +} + +static void handle_stop_vr(const void *buf, uint16_t len) +{ + struct device *dev; + uint8_t status; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+BVRA=0")) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_STOP_VR, status); +} + +static void handle_volume_control(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_volume_control *cmd = buf; + struct device *dev; + uint8_t status; + uint8_t vol; + bool ret; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + /* + * Volume is in the range 0-15. Make sure we send correct value + * to remote device + */ + vol = cmd->volume > 15 ? 15 : cmd->volume; + + switch (cmd->type) { + case HF_CLIENT_VOLUME_TYPE_SPEAKER: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+VGS=%u", vol); + break; + case HF_CLIENT_VOLUME_TYPE_MIC: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+VGM=%u", vol); + break; + default: + ret = false; + break; + } + + status = ret ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_VOLUME_CONTROL, + status); +} + +static void handle_dial(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_dial *cmd = buf; + struct device *dev; + uint8_t status; + bool ret; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->number_len) + goto failed; + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (cmd->number_len > 0) { + if (cmd->number[cmd->number_len - 1] != '\0') + goto failed; + + DBG("Dialing %s", cmd->number); + + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "ATD%s;", cmd->number); + } else { + DBG("Redialing"); + + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+BLDN"); + } + + status = ret ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DIAL, status); + + return; + +failed: + error("Malformed number data, size (%u bytes), terminating", len); + raise(SIGTERM); +} + +static void handle_dial_memory(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_dial_memory *cmd = buf; + struct device *dev; + uint8_t status; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + /* For some reason location in BT HAL is int. Therefore that check */ + if (cmd->location < 0) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL , "ATD>%d;", + cmd->location)) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DIAL_MEMORY, status); +} + +static void handle_call_action(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_call_action *cmd = buf; + struct device *dev; + uint8_t status; + bool ret; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + switch (cmd->action) { + case HAL_HF_CLIENT_ACTION_CHLD_0: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+CHLD=0"); + break; + case HAL_HF_CLIENT_ACTION_CHLD_1: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+CHLD=1"); + break; + case HAL_HF_CLIENT_ACTION_CHLD_2: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, + NULL, "AT+CHLD=2"); + break; + case HAL_HF_CLIENT_ACTION_CHLD_3: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+CHLD=3"); + break; + case HAL_HF_CLIENT_ACTION_CHLD_4: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+CHLD=4"); + break; + case HAL_HF_CLIENT_ACTION_CHLD_1x: + /* Index is int in BT HAL. Let's be paranoid here */ + if (cmd->index <= 0) + ret = false; + else + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, + NULL, "AT+CHLD=1%d", cmd->index); + break; + case HAL_HF_CLIENT_ACTION_CHLD_2x: + /* Index is int in BT HAL. Let's be paranoid here */ + if (cmd->index <= 0) + ret = false; + else + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, + NULL, "AT+CHLD=2%d", cmd->index); + break; + case HAL_HF_CLIENT_ACTION_ATA: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "ATA"); + break; + case HAL_HF_CLIENT_ACTION_CHUP: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+CHUP"); + break; + case HAL_HF_CLIENT_ACTION_BRTH_0: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+BTRH=0"); + break; + case HAL_HF_CLIENT_ACTION_BRTH_1: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+BTRH=1"); + break; + case HAL_HF_CLIENT_ACTION_BRTH_2: + ret = hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, + "AT+BTRH=2"); + break; + default: + error("hf-client: Unknown action %d", cmd->action); + ret = false; + break; + } + + status = ret ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CALL_ACTION, status); +} + +static void handle_query_current_calls(const void *buf, uint16_t len) +{ + struct device *dev; + uint8_t status; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+CLCC")) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_CURRENT_CALLS, + status); +} + +static void handle_query_operator_name(const void *buf, uint16_t len) +{ + struct device *dev; + uint8_t status; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+COPS?")) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_OPERATOR_NAME, + status); +} + +static void handle_retrieve_subscr_info(const void *buf, uint16_t len) +{ + struct device *dev; + uint8_t status; + + DBG(""); + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+CNUM")) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_RETRIEVE_SUBSCR_INFO, + status); +} + +static void handle_send_dtmf(const void *buf, uint16_t len) +{ + const struct hal_cmd_hf_client_send_dtmf *cmd = buf; + struct device *dev; + uint8_t status; + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+VTS=%c", + (char) cmd->tone)) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_SEND_DTMF, status); +} + +static void handle_get_last_vc_tag_num(const void *buf, uint16_t len) +{ + struct device *dev; + uint8_t status; + + dev = find_default_device(); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+BINP=1")) + status = HAL_STATUS_SUCCESS; + else + status = HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_GET_LAST_VOICE_TAG_NUM, status); +} + +static void disconnect_watch(void *user_data) +{ + DBG(""); + + device_destroy(user_data); +} + +static void slc_error(struct device *dev) +{ + error("hf-client: Could not create SLC - dropping connection"); + hfp_hf_disconnect(dev->hf); +} + +static void set_chld_feat(struct device *dev, char *feat) +{ + DBG(" %s", feat); + + if (strcmp(feat, "0") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_REL; + else if (strcmp(feat, "1") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_REL_ACC; + else if (strcmp(feat, "1x") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_REL_X; + else if (strcmp(feat, "2") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_HOLD_ACC; + else if (strcmp(feat, "2x") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_PRIV_X; + else if (strcmp(feat, "3") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_MERGE; + else if (strcmp(feat, "4") == 0) + dev->chld_features |= HAL_HF_CLIENT_CHLD_FEAT_MERGE_DETACH; +} + +static void get_local_codecs_string(struct device *dev, char *buf, + uint8_t len) +{ + int i; + uint8_t offset; + + memset(buf, 0, len); + offset = 0; + + for (i = 0; i < CODECS_COUNT; i++) { + char c[8]; + int l; + + if (!dev->codecs[i].local_supported) + continue; + + memset(c, 0, sizeof(c)); + + l = sprintf(c, "%d,", dev->codecs[i].type); + + if (l > (len - offset - 1)) { + error("hf-client: Codecs cannot fit into buffer"); + return; + } + + strcat(&buf[offset], c); + offset += l; + } +} + +static void bvra_cb(struct hfp_context *context, void *user_data) +{ + struct hal_ev_hf_client_vr_state ev; + unsigned int val; + + if (!hfp_context_get_number(context, &val) || val > 1) + return; + + ev.state = val ? HAL_HF_CLIENT_VR_STARTED : HAL_HF_CLIENT_VR_STOPPED; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_VR_STATE, sizeof(ev), &ev); +} + +static void vgm_cb(struct hfp_context *context, void *user_data) +{ + struct hal_ev_hf_client_volume_changed ev; + unsigned int val; + + if (!hfp_context_get_number(context, &val) || val > 15) + return; + + ev.type = HF_CLIENT_VOLUME_TYPE_MIC; + ev.volume = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_VR_STATE, sizeof(ev), &ev); +} + +static void vgs_cb(struct hfp_context *context, void *user_data) +{ + struct hal_ev_hf_client_volume_changed ev; + unsigned int val; + + if (!hfp_context_get_number(context, &val) || val > 15) + return; + + ev.type = HF_CLIENT_VOLUME_TYPE_SPEAKER; + ev.volume = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_CLIENT_VOLUME_CHANGED, sizeof(ev), &ev); +} + +static void brth_cb(struct hfp_context *context, void *user_data) +{ + struct hal_ev_hf_client_response_and_hold_status ev; + unsigned int val; + + DBG(""); + + if (!hfp_context_get_number(context, &val) || + val > HAL_HF_CLIENT_RESP_AND_HOLD_STATUS_REJECT) { + error("hf-client: incorrect BTRH response "); + return; + } + + ev.status = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_RESPONSE_AND_HOLD_STATUS, + sizeof(ev), &ev); +} + +static void clcc_cb(struct hfp_context *context, void *user_data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_hf_client_current_call *ev = (void *) buf; + unsigned int val; + + DBG(""); + + memset(buf, 0, sizeof(buf)); + + if (!hfp_context_get_number(context, &val)) { + error("hf-client: Could not get index"); + return; + } + + ev->index = val; + + if (!hfp_context_get_number(context, &val) || + val > HAL_HF_CLIENT_DIRECTION_INCOMING) { + error("hf-client: Could not get direction"); + return; + } + + ev->direction = val; + + if (!hfp_context_get_number(context, &val) || + val > HAL_HF_CLIENT_CALL_STATE_HELD_BY_RESP_AND_HOLD) { + error("hf-client: Could not get callstate"); + return; + } + + ev->call_state = val; + + /* Next field is MODE but Android is not interested in this. Skip it */ + if (!hfp_context_get_number(context, &val)) { + error("hf-client: Could not get mode"); + return; + } + + if (!hfp_context_get_number(context, &val) || val > 1) { + error("hf-client: Could not get multiparty"); + return; + } + + ev->multiparty = val; + + if (hfp_context_get_string(context, (char *) &ev->number[0], + MAX_NUMBER_LEN)) + ev->number_len = strlen((char *) ev->number) + 1; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_CURRENT_CALL, + sizeof(*ev) + ev->number_len, ev); +} + +static void ciev_cb(struct hfp_context *context, void *user_data) +{ + struct device *dev = user_data; + unsigned int index, val; + int i; + + DBG(""); + + if (!hfp_context_get_number(context, &index)) + return; + + if (!hfp_context_get_number(context, &val)) + return; + + for (i = 0; i < HFP_INDICATOR_LAST; i++) { + if (dev->ag_ind[i].index != index) + continue; + + if (dev->ag_ind[i].cb) { + dev->ag_ind[i].val = val; + dev->ag_ind[i].cb(val); + return; + } + } +} + +static void cnum_cb(struct hfp_context *context, void *user_data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_hf_client_subscriber_service_info *ev = (void *) buf; + unsigned int service; + + DBG(""); + + /* Alpha field is empty string, just skip it */ + hfp_context_skip_field(context); + + if (!hfp_context_get_string(context, (char *) &ev->name[0], + MAX_NUMBER_LEN)) { + error("hf-client: Could not get number"); + return; + } + + ev->name_len = strlen((char *) &ev->name[0]) + 1; + + /* Type is not used in Android */ + hfp_context_skip_field(context); + + /* Speed field is empty string, just skip it */ + hfp_context_skip_field(context); + + if (!hfp_context_get_number(context, &service)) + return; + + switch (service) { + case 4: + ev->type = HAL_HF_CLIENT_SUBSCR_TYPE_VOICE; + break; + case 5: + ev->type = HAL_HF_CLIENT_SUBSCR_TYPE_FAX; + break; + default: + ev->type = HAL_HF_CLIENT_SUBSCR_TYPE_UNKNOWN; + break; + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_CLIENT_SUBSCRIBER_SERVICE_INFO, + sizeof(*ev) + ev->name_len, ev); +} + +static void cops_cb(struct hfp_context *context, void *user_data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_hf_client_operator_name *ev = (void *) buf; + unsigned int format; + + DBG(""); + + /* Not interested in mode */ + hfp_context_skip_field(context); + + if (!hfp_context_get_number(context, &format)) + return; + + if (format != 0) + info("hf-client: Not correct string format in +COSP"); + + if (!hfp_context_get_string(context, (char *) &ev->name[0], + MAX_OPERATOR_NAME_LEN)) { + error("hf-client: incorrect COPS response"); + return; + } + + ev->name_len = strlen((char *) &ev->name[0]) + 1; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_OPERATOR_NAME, + sizeof(*ev) + ev->name_len, ev); +} + +static void binp_cb(struct hfp_context *context, void *user_data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_hf_client_last_void_call_tag_num *ev = (void *) buf; + char number[33]; + + DBG(""); + + if (!hfp_context_get_string(context, number, sizeof(number))) { + error("hf-client: incorrect COPS response"); + return; + } + + ev->number_len = strlen(number) + 1; + memcpy(ev->number, number, ev->number_len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_CLIENT_LAST_VOICE_CALL_TAG_NUM, + sizeof(*ev) + ev->number_len, ev); +} + +static bool is_codec_supported_localy(struct device *dev, uint8_t codec) +{ + int i; + + for (i = 0; i < CODECS_COUNT; i++) { + if (dev->codecs[i].type != codec) + continue; + + return dev->codecs[i].local_supported; + } + + return false; +} + +static void bcs_resp(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + if (result != HFP_RESULT_OK) + error("hf-client: Error on AT+BCS (err=%u)", result); +} + +static void bcs_cb(struct hfp_context *context, void *user_data) +{ + struct device *dev = user_data; + unsigned int codec; + char codecs_string[8]; + + DBG(""); + + if (!hfp_context_get_number(context, &codec)) + goto failed; + + if (!is_codec_supported_localy(dev, codec)) + goto failed; + + dev->negotiated_codec = codec; + + hfp_hf_send_command(dev->hf, bcs_resp, dev, "AT+BCS=%u", codec); + + return; + +failed: + error("hf-client: Could not get codec"); + + get_local_codecs_string(dev, codecs_string, sizeof(codecs_string)); + + hfp_hf_send_command(dev->hf, bcs_resp, dev, "AT+BCS=%s", codecs_string); +} + +static void slc_completed(struct device *dev) +{ + int i; + struct indicator *ag_ind; + + DBG(""); + + ag_ind = dev->ag_ind; + + device_set_state(dev, HAL_HF_CLIENT_CONN_STATE_SLC_CONNECTED); + + /* Notify Android with indicators */ + for (i = 0; i < HFP_INDICATOR_LAST; i++) { + if (!ag_ind[i].cb) + continue; + + ag_ind[i].cb(ag_ind[i].val); + } + + /* TODO: register unsolicited results handlers */ + + hfp_hf_register(dev->hf, bvra_cb, "+BRVA", dev, NULL); + hfp_hf_register(dev->hf, vgm_cb, "+VGM", dev, NULL); + hfp_hf_register(dev->hf, vgs_cb, "+VGS", dev, NULL); + hfp_hf_register(dev->hf, brth_cb, "+BTRH", dev, NULL); + hfp_hf_register(dev->hf, clcc_cb, "+CLCC", dev, NULL); + hfp_hf_register(dev->hf, ciev_cb, "+CIEV", dev, NULL); + hfp_hf_register(dev->hf, cops_cb, "+COPS", dev, NULL); + hfp_hf_register(dev->hf, cnum_cb, "+CNUM", dev, NULL); + hfp_hf_register(dev->hf, binp_cb, "+BINP", dev, NULL); + hfp_hf_register(dev->hf, bcs_cb, "+BCS", dev, NULL); + + if (!hfp_hf_send_command(dev->hf, cmd_complete_cb, NULL, "AT+COPS=3,0")) + info("hf-client: Could not send AT+COPS=3,0"); +} + +static void slc_chld_cb(struct hfp_context *context, void *user_data) +{ + struct device *dev = user_data; + char feat[3]; + + if (!hfp_context_open_container(context)) + goto failed; + + while (hfp_context_get_unquoted_string(context, feat, sizeof(feat))) + set_chld_feat(dev, feat); + + if (!hfp_context_close_container(context)) + goto failed; + + return; + +failed: + error("hf-client: Error on CHLD response"); + slc_error(dev); +} + +static void slc_chld_resp(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + DBG(""); + + hfp_hf_unregister(dev->hf, "+CHLD"); + + if (result != HFP_RESULT_OK) { + error("hf-client: CHLD error: %d", result); + slc_error(dev); + return; + } + + slc_completed(dev); +} + +static void slc_cmer_resp(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + DBG(""); + + if (result != HFP_RESULT_OK) { + error("hf-client: CMER error: %d", result); + goto failed; + } + + /* Continue with SLC creation */ + if (!(dev->features & HFP_AG_FEAT_3WAY)) { + slc_completed(dev); + return; + } + + if (!hfp_hf_register(dev->hf, slc_chld_cb, "+CHLD", dev, NULL)) { + error("hf-client: Could not register +CHLD"); + goto failed; + } + + if (!hfp_hf_send_command(dev->hf, slc_chld_resp, dev, "AT+CHLD=?")) { + error("hf-client: Could not send AT+CHLD"); + goto failed; + } + + return; + +failed: + slc_error(dev); +} + +static void set_indicator_value(uint8_t index, unsigned int val, + struct indicator *ag_ind) +{ + int i; + + for (i = 0; i < HFP_INDICATOR_LAST; i++) { + if (index != ag_ind[i].index) + continue; + + ag_ind[i].val = val; + ag_ind[i].cb(val); + return; + } +} + +static void slc_cind_status_cb(struct hfp_context *context, + void *user_data) +{ + struct device *dev = user_data; + uint8_t index = 1; + + DBG(""); + + while (hfp_context_has_next(context)) { + uint32_t val; + + if (!hfp_context_get_number(context, &val)) { + error("hf-client: Error on CIND status response"); + return; + } + + set_indicator_value(index++, val, dev->ag_ind); + } +} + +static void slc_cind_status_resp(enum hfp_result result, + enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + DBG(""); + + hfp_hf_unregister(dev->hf, "+CIND"); + + if (result != HFP_RESULT_OK) { + error("hf-client: CIND error: %d", result); + goto failed; + } + + /* Continue with SLC creation */ + if (!hfp_hf_send_command(dev->hf, slc_cmer_resp, dev, + "AT+CMER=3,0,0,1")) { + error("hf-client: Counld not send AT+CMER"); + goto failed; + } + + return; + +failed: + slc_error(dev); +} + +static void slc_cind_resp(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + DBG(""); + + hfp_hf_unregister(dev->hf, "+CIND"); + + if (result != HFP_RESULT_OK) { + error("hf-client: CIND error: %d", result); + goto failed; + } + + /* Continue with SLC creation */ + if (!hfp_hf_register(dev->hf, slc_cind_status_cb, "+CIND", dev, + NULL)) { + error("hf-client: Counld not register +CIND"); + goto failed; + } + + if (!hfp_hf_send_command(dev->hf, slc_cind_status_resp, dev, + "AT+CIND?")) { + error("hf-client: Counld not send AT+CIND?"); + goto failed; + } + + return; + +failed: + slc_error(dev); +} + +static void ciev_service_cb(uint8_t val) +{ + struct hal_ev_hf_client_net_state ev; + + DBG(""); + + if (val > HAL_HF_CLIENT_NET_ROAMING_TYPE_ROAMING) { + error("hf-client: Incorrect state %u:", val); + return; + } + + ev.state = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_NET_STATE, sizeof(ev), &ev); +} + +static void ciev_call_cb(uint8_t val) +{ + struct hal_ev_hf_client_call_indicator ev; + + DBG(""); + + if (val > HAL_HF_CLIENT_CALL_IND_CALL_IN_PROGERSS) { + error("hf-client: Incorrect call state %u:", val); + return; + } + + ev.call = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_CALL_INDICATOR, sizeof(ev), &ev); +} + +static void ciev_callsetup_cb(uint8_t val) +{ + struct hal_ev_hf_client_call_setup_indicator ev; + + DBG(""); + + if (val > HAL_HF_CLIENT_CALL_SETUP_ALERTING) { + error("hf-client: Incorrect call setup state %u:", val); + return; + } + + ev.call_setup = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_CALL_SETUP_INDICATOR, + sizeof(ev), &ev); +} + +static void ciev_callheld_cb(uint8_t val) +{ + struct hal_ev_hf_client_call_held_indicator ev; + + DBG(""); + + if (val > HAL_HF_CLIENT_CALL_SETUP_IND_HOLD) { + error("hf-client: Incorrect call held state %u:", val); + return; + } + + ev.call_held = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_CALL_HELD_INDICATOR, + sizeof(ev), &ev); +} + +static void ciev_signal_cb(uint8_t val) +{ + struct hal_ev_hf_client_net_signal_strength ev; + + DBG(""); + + if (val > 5) { + error("hf-client: Incorrect signal value %u:", val); + return; + } + + ev.signal_strength = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_NET_SIGNAL_STRENGTH, + sizeof(ev), &ev); +} + +static void ciev_roam_cb(uint8_t val) +{ + struct hal_ev_hf_client_net_roaming_type ev; + + DBG(""); + + if (val > HAL_HF_CLIENT_NET_ROAMING_TYPE_ROAMING) { + error("hf-client: Incorrect roaming state %u:", val); + return; + } + + ev.state = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_NET_ROAMING_TYPE, + sizeof(ev), &ev); +} + +static void ciev_battchg_cb(uint8_t val) +{ + struct hal_ev_hf_client_battery_level ev; + + DBG(""); + + if (val > 5) { + error("hf-client: Incorrect battery charge value %u:", val); + return; + } + + ev.battery_level = val; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_EV_HF_CLIENT_BATTERY_LEVEL, sizeof(ev), &ev); +} + +static void set_indicator_parameters(uint8_t index, const char *indicator, + unsigned int min, + unsigned int max, + struct indicator *ag_ind) +{ + DBG("%s, %i", indicator, index); + + /* TODO: Verify min/max values ? */ + + if (strcmp("service", indicator) == 0) { + ag_ind[HFP_INDICATOR_SERVICE].index = index; + ag_ind[HFP_INDICATOR_SERVICE].min = min; + ag_ind[HFP_INDICATOR_SERVICE].max = max; + ag_ind[HFP_INDICATOR_SERVICE].cb = ciev_service_cb; + return; + } + + if (strcmp("call", indicator) == 0) { + ag_ind[HFP_INDICATOR_CALL].index = index; + ag_ind[HFP_INDICATOR_CALL].min = min; + ag_ind[HFP_INDICATOR_CALL].max = max; + ag_ind[HFP_INDICATOR_CALL].cb = ciev_call_cb; + return; + } + + if (strcmp("callsetup", indicator) == 0) { + ag_ind[HFP_INDICATOR_CALLSETUP].index = index; + ag_ind[HFP_INDICATOR_CALLSETUP].min = min; + ag_ind[HFP_INDICATOR_CALLSETUP].max = max; + ag_ind[HFP_INDICATOR_CALLSETUP].cb = ciev_callsetup_cb; + return; + } + + if (strcmp("callheld", indicator) == 0) { + ag_ind[HFP_INDICATOR_CALLHELD].index = index; + ag_ind[HFP_INDICATOR_CALLHELD].min = min; + ag_ind[HFP_INDICATOR_CALLHELD].max = max; + ag_ind[HFP_INDICATOR_CALLHELD].cb = ciev_callheld_cb; + return; + } + + if (strcmp("signal", indicator) == 0) { + ag_ind[HFP_INDICATOR_SIGNAL].index = index; + ag_ind[HFP_INDICATOR_SIGNAL].min = min; + ag_ind[HFP_INDICATOR_SIGNAL].max = max; + ag_ind[HFP_INDICATOR_SIGNAL].cb = ciev_signal_cb; + return; + } + + if (strcmp("roam", indicator) == 0) { + ag_ind[HFP_INDICATOR_ROAM].index = index; + ag_ind[HFP_INDICATOR_ROAM].min = min; + ag_ind[HFP_INDICATOR_ROAM].max = max; + ag_ind[HFP_INDICATOR_ROAM].cb = ciev_roam_cb; + return; + } + + if (strcmp("battchg", indicator) == 0) { + ag_ind[HFP_INDICATOR_BATTCHG].index = index; + ag_ind[HFP_INDICATOR_BATTCHG].min = min; + ag_ind[HFP_INDICATOR_BATTCHG].max = max; + ag_ind[HFP_INDICATOR_BATTCHG].cb = ciev_battchg_cb; + return; + } + + error("hf-client: Unknown indicator: %s", indicator); +} + +static void slc_cind_cb(struct hfp_context *context, void *user_data) +{ + struct device *dev = user_data; + int index = 1; + + DBG(""); + + while (hfp_context_has_next(context)) { + char name[255]; + unsigned int min, max; + + /* e.g ("callsetup",(0-3)) */ + if (!hfp_context_open_container(context)) + break; + + if (!hfp_context_get_string(context, name, sizeof(name))) { + error("hf-client: Could not get string"); + goto failed; + } + + if (!hfp_context_open_container(context)) { + error("hf-client: Could not open container"); + goto failed; + } + + if (!hfp_context_get_range(context, &min, &max)) { + if (!hfp_context_get_number(context, &min)) { + error("hf-client: Could not get number"); + goto failed; + } + + if (!hfp_context_get_number(context, &max)) { + error("hf-client: Could not get number"); + goto failed; + } + } + + if (!hfp_context_close_container(context)) { + error("hf-client: Could not close container"); + goto failed; + } + + if (!hfp_context_close_container(context)) { + error("hf-client: Could not close container"); + goto failed; + } + + set_indicator_parameters(index, name, min, max, dev->ag_ind); + index++; + } + + return; + +failed: + error("hf-client: Error on CIND response"); + slc_error(dev); +} + +static void slc_bac_resp(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + DBG(""); + + if (result != HFP_RESULT_OK) + goto failed; + + /* Continue with SLC creation */ + if (!hfp_hf_register(dev->hf, slc_cind_cb, "+CIND", dev, NULL)) { + error("hf-client: Could not register for +CIND"); + goto failed; + } + + if (!hfp_hf_send_command(dev->hf, slc_cind_resp, dev, "AT+CIND=?")) + goto failed; + + return; + +failed: + error("hf-client: Error on BAC response"); + slc_error(dev); +} + +static bool send_supported_codecs(struct device *dev) +{ + char codecs_string[8]; + char bac[16]; + + memset(bac, 0, sizeof(bac)); + + strcpy(bac, "AT+BAC="); + + get_local_codecs_string(dev, codecs_string, sizeof(codecs_string)); + strcat(bac, codecs_string); + + return hfp_hf_send_command(dev->hf, slc_bac_resp, dev, bac); +} + +static void slc_brsf_cb(struct hfp_context *context, void *user_data) +{ + unsigned int feat; + struct device *dev = user_data; + + DBG(""); + + if (hfp_context_get_number(context, &feat)) + dev->features = feat; +} + +static void slc_brsf_resp(enum hfp_result result, enum hfp_error cme_err, + void *user_data) +{ + struct device *dev = user_data; + + hfp_hf_unregister(dev->hf, "+BRSF"); + + if (result != HFP_RESULT_OK) { + error("hf-client: BRSF error: %d", result); + goto failed; + } + + /* Continue with SLC creation */ + if (codec_negotiation_supported(dev)) { + if (send_supported_codecs(dev)) + return; + + error("hf-client: Could not send BAC command"); + goto failed; + } + + /* No WBS on remote side. Continue with indicators */ + if (!hfp_hf_register(dev->hf, slc_cind_cb, "+CIND", dev, NULL)) { + error("hf-client: Could not register for +CIND"); + goto failed; + } + + if (!hfp_hf_send_command(dev->hf, slc_cind_resp, dev, "AT+CIND=?")) { + error("hf-client: Could not send AT+CIND command"); + goto failed; + } + + return; + +failed: + slc_error(dev); +} + +static bool create_slc(struct device *dev) +{ + DBG(""); + + if (!hfp_hf_register(dev->hf, slc_brsf_cb, "+BRSF", dev, NULL)) + return false; + + return hfp_hf_send_command(dev->hf, slc_brsf_resp, dev, "AT+BRSF=%u", + hfp_hf_features); +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct device *dev = user_data; + + DBG(""); + + if (err) { + error("hf-client: connect failed (%s)", err->message); + goto failed; + } + + dev->hf = hfp_hf_new(g_io_channel_unix_get_fd(chan)); + if (!dev->hf) { + error("hf-client: Could not create hfp io"); + goto failed; + } + + g_io_channel_set_close_on_unref(chan, FALSE); + + hfp_hf_set_close_on_unref(dev->hf, true); + hfp_hf_set_disconnect_handler(dev->hf, disconnect_watch, dev, NULL); + + if (!create_slc(dev)) { + error("hf-client: Could not start SLC creation"); + hfp_hf_disconnect(dev->hf); + goto failed; + } + + device_set_state(dev, HAL_HF_CLIENT_CONN_STATE_CONNECTED); + + return; + +failed: + g_io_channel_shutdown(chan, TRUE, NULL); + device_destroy(dev); +} + +static void sdp_hfp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + sdp_list_t *protos, *classes; + struct device *dev = data; + GError *gerr = NULL; + GIOChannel *io; + uuid_t uuid; + int channel; + + DBG(""); + + if (err < 0) { + error("hf-client: unable to get SDP record: %s", + strerror(-err)); + goto failed; + } + + if (!recs || !recs->data) { + info("hf-client: no HFP SDP records found"); + goto failed; + } + + if (sdp_get_service_classes(recs->data, &classes) < 0 || !classes) { + error("hf-client: unable to get service classes from record"); + goto failed; + } + + /* TODO read remote version? */ + + 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) { + error("hf-client: invalid service record or not HFP"); + goto failed; + } + + if (sdp_get_access_protos(recs->data, &protos) < 0) { + error("hf-client: unable to get access protocols from record"); + sdp_list_free(classes, free); + goto failed; + } + + channel = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + if (channel <= 0) { + error("hf-client: unable to get RFCOMM channel from record"); + goto failed; + } + + io = bt_io_connect(connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_CHANNEL, channel, + BT_IO_OPT_INVALID); + if (!io) { + error("hf-client: unable to connect: %s", gerr->message); + g_error_free(gerr); + goto failed; + } + + g_io_channel_unref(io); + return; + +failed: + device_destroy(dev); +} + +static int sdp_search_hfp(struct device *dev) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID); + + return bt_search_service(&adapter_addr, &dev->bdaddr, &uuid, + sdp_hfp_search_cb, dev, NULL, 0); +} + +static void handle_connect(const void *buf, uint16_t len) +{ + struct device *dev; + const struct hal_cmd_hf_client_connect *cmd = buf; + uint32_t status; + bdaddr_t bdaddr; + char addr[18]; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &bdaddr); + + ba2str(&bdaddr, addr); + DBG("connecting to %s", addr); + + dev = get_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->state != HAL_HF_CLIENT_CONN_STATE_DISCONNECTED) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (sdp_search_hfp(dev) < 0) { + status = HAL_STATUS_FAILED; + device_destroy(dev); + goto done; + } + + device_set_state(dev, HAL_HF_CLIENT_CONN_STATE_CONNECTING); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT, status); +} + +static void confirm_cb(GIOChannel *chan, gpointer data) +{ + struct device *dev; + char address[18]; + bdaddr_t bdaddr; + GError *err = NULL; + + bt_io_get(chan, &err, + BT_IO_OPT_DEST, address, + BT_IO_OPT_DEST_BDADDR, &bdaddr, + BT_IO_OPT_INVALID); + if (err) { + error("hf-client: confirm failed (%s)", err->message); + g_error_free(err); + goto drop; + } + + DBG("Incoming connection from %s", address); + + dev = get_device(&bdaddr); + if (!dev) { + error("hf-client: There is other AG connected"); + goto drop; + } + + if (dev->state != HAL_HF_CLIENT_CONN_STATE_DISCONNECTED) { + /* TODO: Handle colision */ + error("hf-client: Connections is up or ongoing ?"); + goto drop; + } + + device_set_state(dev, HAL_HF_CLIENT_CONN_STATE_CONNECTING); + + if (!bt_io_accept(chan, connect_cb, dev, NULL, NULL)) { + error("hf-client: failed to accept connection"); + device_destroy(dev); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_HF_CLIENT_CONNECT */ + { handle_connect, false, + sizeof(struct hal_cmd_hf_client_connect) }, + /* HAL_OP_HF_CLIENT_DISCONNECT */ + { handle_disconnect, false, + sizeof(struct hal_cmd_hf_client_disconnect) }, + /* HAL_OP_HF_CLIENT_CONNECT_AUDIO */ + { handle_connect_audio, false, + sizeof(struct hal_cmd_hf_client_connect_audio) }, + /* HAL_OP_HF_CLIENT_DISCONNECT_AUDIO */ + { handle_disconnect_audio, false, + sizeof(struct hal_cmd_hf_client_disconnect_audio) }, + /* define HAL_OP_HF_CLIENT_START_VR */ + { handle_start_vr, false, 0 }, + /* define HAL_OP_HF_CLIENT_STOP_VR */ + { handle_stop_vr, false, 0 }, + /* HAL_OP_HF_CLIENT_VOLUME_CONTROL */ + { handle_volume_control, false, + sizeof(struct hal_cmd_hf_client_volume_control) }, + /* HAL_OP_HF_CLIENT_DIAL */ + { handle_dial, true, sizeof(struct hal_cmd_hf_client_dial) }, + /* HAL_OP_HF_CLIENT_DIAL_MEMORY */ + { handle_dial_memory, false, + sizeof(struct hal_cmd_hf_client_dial_memory) }, + /* HAL_OP_HF_CLIENT_CALL_ACTION */ + { handle_call_action, false, + sizeof(struct hal_cmd_hf_client_call_action) }, + /* HAL_OP_HF_CLIENT_QUERY_CURRENT_CALLS */ + { handle_query_current_calls, false, 0 }, + /* HAL_OP_HF_CLIENT_QUERY_OPERATOR_NAME */ + { handle_query_operator_name, false, 0 }, + /* HAL_OP_HF_CLIENT_RETRIEVE_SUBSCR_INFO */ + { handle_retrieve_subscr_info, false, 0 }, + /* HAL_OP_HF_CLIENT_SEND_DTMF */ + { handle_send_dtmf, false, + sizeof(struct hal_cmd_hf_client_send_dtmf) }, + /* HAL_OP_HF_CLIENT_GET_LAST_VOICE_TAG_NUM */ + { handle_get_last_vc_tag_num, false, 0 }, +}; + +static sdp_record_t *hfp_hf_record(void) +{ + 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; + uint16_t sdpfeat; + uint8_t ch = HFP_HF_CHANNEL; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &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 = 0x0106; + pfseq = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap_uuid); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(NULL, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + /* Codec Negotiation bit in SDP feature is different then in BRSF */ + sdpfeat = hfp_hf_features & 0x0000003F; + if (hfp_hf_features & HFP_HF_FEAT_CODEC) + sdpfeat |= 0x00000020; + else + sdpfeat &= ~0x00000020; + + features = sdp_data_alloc(SDP_UINT16, &sdpfeat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free unit", NULL, NULL); + + sdp_data_free(channel); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static bool enable_hf_client(void) +{ + sdp_record_t *rec; + GError *err = NULL; + + hfp_hf_server = bt_io_listen(NULL, confirm_cb, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_CHANNEL, HFP_HF_CHANNEL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (!hfp_hf_server) { + error("hf-client: Failed to listen on Handsfree rfcomm: %s", + err->message); + g_error_free(err); + return false; + } + + hfp_hf_features = HFP_HF_FEATURES; + + rec = hfp_hf_record(); + if (!rec) { + error("hf-client: Could not create service record"); + goto failed; + } + + if (bt_adapter_add_record(rec, 0) < 0) { + error("hf-client: Failed to register service record"); + sdp_record_free(rec); + goto failed; + } + + hfp_hf_record_id = rec->handle; + + return true; + +failed: + g_io_channel_shutdown(hfp_hf_server, TRUE, NULL); + g_io_channel_unref(hfp_hf_server); + hfp_hf_server = NULL; + + return false; +} + +static void cleanup_hfp_hf(void) +{ + if (hfp_hf_server) { + g_io_channel_shutdown(hfp_hf_server, TRUE, NULL); + g_io_channel_unref(hfp_hf_server); + hfp_hf_server = NULL; + } + + if (hfp_hf_record_id > 0) { + bt_adapter_remove_record(hfp_hf_record_id); + hfp_hf_record_id = 0; + } + + if (sco) { + bt_sco_unref(sco); + sco = NULL; + } +} + +static bool confirm_sco_cb(const bdaddr_t *addr, uint16_t *voice_settings) +{ + struct device *dev; + + DBG(""); + + dev = find_device(addr); + if (!dev || dev->state != HAL_HF_CLIENT_CONN_STATE_SLC_CONNECTED) { + error("hf-client: No device or SLC not ready"); + return false; + } + + set_audio_state(dev, HAL_HF_CLIENT_AUDIO_STATE_CONNECTING); + + if (codec_negotiation_supported(dev) && + dev->negotiated_codec != CODEC_ID_CVSD) + *voice_settings = BT_VOICE_TRANSPARENT; + else + *voice_settings = BT_VOICE_CVSD_16BIT; + + return true; +} + +static void connect_sco_cb(enum sco_status status, const bdaddr_t *addr) +{ + struct device *dev; + uint8_t audio_state; + + DBG("SCO Status %u", status); + + /* Device shall be there, just sanity check */ + dev = find_device(addr); + if (!dev) { + error("hf-client: There is no device?"); + return; + } + + if (status != SCO_STATUS_OK) { + audio_state = HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED; + goto done; + } + + if (dev->negotiated_codec == CODEC_ID_MSBC) + audio_state = HAL_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC; + else + audio_state = HAL_HF_CLIENT_AUDIO_STATE_CONNECTED; + +done: + set_audio_state(dev, audio_state); +} + +static void disconnect_sco_cb(const bdaddr_t *addr) +{ + struct device *dev; + + DBG(""); + + dev = find_device(addr); + if (!dev) { + error("hf-client: No device"); + return; + } + + set_audio_state(dev, HAL_HF_CLIENT_AUDIO_STATE_DISCONNECTED); +} + +bool bt_hf_client_register(struct ipc *ipc, const bdaddr_t *addr) +{ + DBG(""); + + devices = queue_new(); + + bacpy(&adapter_addr, addr); + + if (!enable_hf_client()) + goto failed; + + sco = bt_sco_new(addr); + if (!sco) { + error("hf-client: Cannot create SCO. HFP AG is in use ?"); + goto failed; + } + + bt_sco_set_confirm_cb(sco, confirm_sco_cb); + bt_sco_set_connect_cb(sco, connect_sco_cb); + bt_sco_set_disconnect_cb(sco, disconnect_sco_cb); + + hal_ipc = ipc; + ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE_CLIENT, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; + +failed: + cleanup_hfp_hf(); + queue_destroy(devices, free); + devices = NULL; + + return false; +} + +void bt_hf_client_unregister(void) +{ + DBG(""); + + cleanup_hfp_hf(); + + queue_destroy(devices, (void *) device_destroy); + devices = NULL; + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_HANDSFREE); + hal_ipc = NULL; +}
diff --git a/android/handsfree-client.h b/android/handsfree-client.h new file mode 100644 index 0000000..1eb69ff --- /dev/null +++ b/android/handsfree-client.h
@@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_hf_client_register(struct ipc *ipc, const bdaddr_t *addr); +void bt_hf_client_unregister(void);
diff --git a/android/handsfree.c b/android/handsfree.c new file mode 100644 index 0000000..cb348ab --- /dev/null +++ b/android/handsfree.c
@@ -0,0 +1,3031 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "src/sdp-client.h" +#include "src/uuid-helper.h" +#include "src/shared/hfp.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "btio/btio.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "handsfree.h" +#include "bluetooth.h" +#include "src/log.h" +#include "utils.h" +#include "sco-msg.h" +#include "sco.h" + +#define HSP_AG_CHANNEL 12 +#define HFP_AG_CHANNEL 13 + +#define HFP_AG_FEAT_3WAY 0x00000001 +#define HFP_AG_FEAT_ECNR 0x00000002 +#define HFP_AG_FEAT_VR 0x00000004 +#define HFP_AG_FEAT_INBAND 0x00000008 +#define HFP_AG_FEAT_VTAG 0x00000010 +#define HFP_AG_FEAT_REJ_CALL 0x00000020 +#define HFP_AG_FEAT_ECS 0x00000040 +#define HFP_AG_FEAT_ECC 0x00000080 +#define HFP_AG_FEAT_EXT_ERR 0x00000100 +#define HFP_AG_FEAT_CODEC 0x00000200 + +#define HFP_HF_FEAT_ECNR 0x00000001 +#define HFP_HF_FEAT_3WAY 0x00000002 +#define HFP_HF_FEAT_CLI 0x00000004 +#define HFP_HF_FEAT_VR 0x00000008 +#define HFP_HF_FEAT_RVC 0x00000010 +#define HFP_HF_FEAT_ECS 0x00000020 +#define HFP_HF_FEAT_ECC 0x00000040 +#define HFP_HF_FEAT_CODEC 0x00000080 + +#define HFP_AG_FEATURES (HFP_AG_FEAT_3WAY | HFP_AG_FEAT_ECNR |\ + HFP_AG_FEAT_VR | HFP_AG_FEAT_REJ_CALL |\ + HFP_AG_FEAT_ECS | HFP_AG_FEAT_EXT_ERR) + +#define HFP_AG_CHLD "0,1,2,3" + +/* offsets in indicators table, should be incremented when sending CIEV */ +#define IND_SERVICE 0 +#define IND_CALL 1 +#define IND_CALLSETUP 2 +#define IND_CALLHELD 3 +#define IND_SIGNAL 4 +#define IND_ROAM 5 +#define IND_BATTCHG 6 +#define IND_COUNT (IND_BATTCHG + 1) + +#define RING_TIMEOUT 2 + +#define CVSD_OFFSET 0 +#define MSBC_OFFSET 1 +#define CODECS_COUNT (MSBC_OFFSET + 1) + +#define CODEC_ID_CVSD 0x01 +#define CODEC_ID_MSBC 0x02 + +struct indicator { + const char *name; + int min; + int max; + int val; + bool always_active; + bool active; +}; + +struct hfp_codec { + uint8_t type; + bool local_supported; + bool remote_supported; +}; + +struct hf_device { + bdaddr_t bdaddr; + uint8_t state; + uint8_t audio_state; + uint32_t features; + + bool clip_enabled; + bool cmee_enabled; + bool ccwa_enabled; + bool indicators_enabled; + struct indicator inds[IND_COUNT]; + int num_active; + int num_held; + int setup_state; + guint call_hanging_up; + + uint8_t negotiated_codec; + uint8_t proposed_codec; + struct hfp_codec codecs[CODECS_COUNT]; + + guint ring; + char *clip; + bool hsp; + + struct hfp_gw *gw; + guint delay_sco; +}; + +static const struct indicator inds_defaults[] = { + { "service", 0, 1, 0, false, true }, + { "call", 0, 1, 0, true, true }, + { "callsetup", 0, 3, 0, true, true }, + { "callheld", 0, 2, 0, true, true }, + { "signal", 0, 5, 0, false, true }, + { "roam", 0, 1, 0, false, true }, + { "battchg", 0, 5, 0, false, true }, +}; + +static const struct hfp_codec codecs_defaults[] = { + { CODEC_ID_CVSD, true, false}, + { CODEC_ID_MSBC, false, false}, +}; + +static struct queue *devices = NULL; + +static uint32_t hfp_ag_features = 0; + +static bdaddr_t adapter_addr; + +static struct ipc *hal_ipc = NULL; +static struct ipc *sco_ipc = NULL; + +static uint32_t hfp_record_id = 0; +static GIOChannel *hfp_server = NULL; + +static uint32_t hsp_record_id = 0; +static GIOChannel *hsp_server = NULL; + +static struct bt_sco *sco = NULL; + +static unsigned int max_hfp_clients = 0; + +static void set_state(struct hf_device *dev, uint8_t state) +{ + struct hal_ev_handsfree_conn_state ev; + char address[18]; + + if (dev->state == state) + return; + + dev->state = state; + + ba2str(&dev->bdaddr, address); + DBG("device %s state %u", address, state); + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_CONN_STATE, sizeof(ev), &ev); +} + +static void set_audio_state(struct hf_device *dev, uint8_t state) +{ + struct hal_ev_handsfree_audio_state ev; + char address[18]; + + if (dev->audio_state == state) + return; + + dev->audio_state = state; + + ba2str(&dev->bdaddr, address); + DBG("device %s audio state %u", address, state); + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_AUDIO_STATE, sizeof(ev), &ev); +} + +static void init_codecs(struct hf_device *dev) +{ + memcpy(dev->codecs, codecs_defaults, sizeof(dev->codecs)); + + if (hfp_ag_features & HFP_AG_FEAT_CODEC) + dev->codecs[MSBC_OFFSET].local_supported = true; +} + +static struct hf_device *device_create(const bdaddr_t *bdaddr) +{ + struct hf_device *dev; + + dev = new0(struct hf_device, 1); + + bacpy(&dev->bdaddr, bdaddr); + dev->setup_state = HAL_HANDSFREE_CALL_STATE_IDLE; + dev->state = HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED; + dev->audio_state = HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED; + + memcpy(dev->inds, inds_defaults, sizeof(dev->inds)); + + init_codecs(dev); + + queue_push_head(devices, dev); + + return dev; +} + +static void device_destroy(struct hf_device *dev) +{ + hfp_gw_unref(dev->gw); + + if (dev->delay_sco) + g_source_remove(dev->delay_sco); + + if (dev->audio_state == HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTED) + bt_sco_disconnect(sco); + + if (dev->ring) + g_source_remove(dev->ring); + + g_free(dev->clip); + + if (dev->call_hanging_up) + g_source_remove(dev->call_hanging_up); + + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED); + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED); + + queue_remove(devices, dev); + free(dev); +} + +static bool match_by_bdaddr(const void *data, const void *match_data) +{ + const struct hf_device *dev = data; + const bdaddr_t *addr = match_data; + + return !bacmp(&dev->bdaddr, addr); +} + +static struct hf_device *find_device(const bdaddr_t *bdaddr) +{ + if (!bacmp(bdaddr, BDADDR_ANY)) + return queue_peek_head(devices); + + return queue_find(devices, match_by_bdaddr, bdaddr); +} + +static struct hf_device *get_device(const bdaddr_t *bdaddr) +{ + struct hf_device *dev; + + dev = find_device(bdaddr); + if (dev) + return dev; + + if (queue_length(devices) == max_hfp_clients) + return NULL; + + return device_create(bdaddr); +} + +static void disconnect_watch(void *user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + device_destroy(dev); +} + +static void at_cmd_unknown(const char *command, void *user_data) +{ + struct hf_device *dev = user_data; + uint8_t buf[IPC_MTU]; + struct hal_ev_handsfree_unknown_at *ev = (void *) buf; + + bdaddr2android(&dev->bdaddr, ev->bdaddr); + + /* copy while string including terminating NULL */ + ev->len = strlen(command) + 1; + + if (ev->len > IPC_MTU - sizeof(*ev)) { + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + return; + } + + memcpy(ev->buf, command, ev->len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_UNKNOWN_AT, sizeof(*ev) + ev->len, ev); +} + +static void at_cmd_vgm(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_volume ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val > 15) + break; + + if (hfp_context_has_next(context)) + break; + + ev.type = HAL_HANDSFREE_VOLUME_TYPE_MIC; + ev.volume = val; + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_VOLUME, sizeof(ev), &ev); + + /* Framework is not replying with result for AT+VGM */ + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_vgs(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_volume ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val > 15) + break; + + if (hfp_context_has_next(context)) + break; + + ev.type = HAL_HANDSFREE_VOLUME_TYPE_SPEAKER; + ev.volume = val; + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_VOLUME, sizeof(ev), &ev); + + /* Framework is not replying with result for AT+VGS */ + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_cops(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_cops ev; + unsigned int val; + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val != 3) + break; + + if (!hfp_context_get_number(context, &val) || val != 0) + break; + + if (hfp_context_has_next(context)) + break; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_COPS, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_bia(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int val, i, def; + bool tmp[IND_COUNT]; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + for (i = 0; i < IND_COUNT; i++) + tmp[i] = dev->inds[i].active; + + i = 0; + + do { + def = (i < IND_COUNT) ? dev->inds[i].active : 0; + + if (!hfp_context_get_number_default(context, &val, def)) + goto failed; + + if (val > 1) + goto failed; + + if (i < IND_COUNT) { + tmp[i] = val || dev->inds[i].always_active; + i++; + } + } while (hfp_context_has_next(context)); + + for (i = 0; i < IND_COUNT; i++) + dev->inds[i].active = tmp[i]; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + +failed: + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_a(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_answer ev; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_COMMAND: + if (hfp_context_has_next(context)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_ANSWER, sizeof(ev), &ev); + + /* Framework is not replying with result for ATA */ + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_SET: + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_d(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + char buf[IPC_MTU]; + struct hal_ev_handsfree_dial *ev = (void *) buf; + int cnt; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_unquoted_string(context, + (char *) ev->number, 255)) + break; + + bdaddr2android(&dev->bdaddr, ev->bdaddr); + + ev->number_len = strlen((char *) ev->number); + + if (ev->number[ev->number_len - 1] != ';') + break; + + if (ev->number[0] == '>') + cnt = strspn((char *) ev->number + 1, "0123456789") + 1; + else + cnt = strspn((char *) ev->number, "0123456789ABC*#+"); + + if (cnt != ev->number_len - 1) + break; + + ev->number_len++; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_DIAL, + sizeof(*ev) + ev->number_len, ev); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_ccwa(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val > 1) + break; + + if (hfp_context_has_next(context)) + break; + + dev->ccwa_enabled = val; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_chup(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_hangup ev; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_COMMAND: + if (hfp_context_has_next(context)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_HANGUP, sizeof(ev), &ev); + + /* Framework is not replying with result for AT+CHUP */ + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_SET: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_clcc(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_clcc ev; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_COMMAND: + if (hfp_context_has_next(context)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_CLCC, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_SET: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_cmee(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val > 1) + break; + + if (hfp_context_has_next(context)) + break; + + dev->cmee_enabled = val; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_clip(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val > 1) + break; + + if (hfp_context_has_next(context)) + break; + + dev->clip_enabled = val; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_vts(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_dtmf ev; + char str[2]; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_unquoted_string(context, str, 2)) + break; + + if (!((str[0] >= '0' && str[0] <= '9') || + (str[0] >= 'A' && str[0] <= 'D') || + str[0] == '*' || str[0] == '#')) + break; + + if (hfp_context_has_next(context)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + ev.tone = str[0]; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_DTMF, sizeof(ev), &ev); + + /* Framework is not replying with result for AT+VTS */ + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_cnum(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_cnum ev; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_COMMAND: + if (hfp_context_has_next(context)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_CNUM, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_SET: + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_binp(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + /* TODO */ + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_bldn(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_dial ev; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_COMMAND: + if (hfp_context_has_next(context)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + ev.number_len = 0; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_DIAL, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_SET: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_bvra(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_vr_state ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(context, &val) || val > 1) + break; + + if (hfp_context_has_next(context)) + break; + + if (val) + ev.state = HAL_HANDSFREE_VR_STARTED; + else + ev.state = HAL_HANDSFREE_VR_STOPPED; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_VR, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_nrec(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_nrec ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + /* + * Android HAL defines start and stop parameter for NREC + * callback, but spec allows HF to only disable AG's NREC + * feature for SLC duration. Follow spec here. + */ + if (!hfp_context_get_number(context, &val) || val != 0) + break; + + if (hfp_context_has_next(context)) + break; + + ev.nrec = HAL_HANDSFREE_NREC_STOP; + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_NREC, sizeof(ev), &ev); + + /* Framework is not replying with context for AT+NREC */ + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_bsir(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + /* TODO */ + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_btrh(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + /* TODO */ + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void disconnect_sco_cb(const bdaddr_t *addr) +{ + struct hf_device *dev; + + DBG(""); + + dev = find_device(addr); + if (!dev) { + error("handsfree: Could not find device"); + return; + } + + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED); +} + +static void select_codec(struct hf_device *dev, uint8_t codec_type) +{ + uint8_t type = CODEC_ID_CVSD; + int i; + + if (codec_type > 0) { + type = codec_type; + goto done; + } + + for (i = CODECS_COUNT - 1; i >= CVSD_OFFSET; i--) { + if (!dev->codecs[i].local_supported) + continue; + + if (!dev->codecs[i].remote_supported) + continue; + + type = dev->codecs[i].type; + break; + } + +done: + dev->proposed_codec = type; + + hfp_gw_send_info(dev->gw, "+BCS: %u", type); +} + +static bool codec_negotiation_supported(struct hf_device *dev) +{ + return (dev->features & HFP_HF_FEAT_CODEC) && + (hfp_ag_features & HFP_AG_FEAT_CODEC); +} + +static void connect_sco_cb(enum sco_status status, const bdaddr_t *addr) +{ + struct hf_device *dev; + + dev = find_device(addr); + if (!dev) { + error("handsfree: Connect sco failed, no device?"); + return; + } + + if (status == SCO_STATUS_OK) { + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTED); + return; + } + + /* Try fallback to CVSD first */ + if (codec_negotiation_supported(dev) && + dev->negotiated_codec != CODEC_ID_CVSD) { + info("handsfree: trying fallback with CVSD"); + select_codec(dev, CODEC_ID_CVSD); + return; + } + + error("handsfree: audio connect failed"); + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED); +} + +static bool connect_sco(struct hf_device *dev) +{ + uint16_t voice_settings; + + if (codec_negotiation_supported(dev) && + dev->negotiated_codec != CODEC_ID_CVSD) + voice_settings = BT_VOICE_TRANSPARENT; + else + voice_settings = BT_VOICE_CVSD_16BIT; + + if (!bt_sco_connect(sco, &dev->bdaddr, voice_settings)) + return false; + + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTING); + + return true; +} + +static gboolean connect_sco_delayed(void *data) +{ + struct hf_device *dev = data; + + DBG(""); + + dev->delay_sco = 0; + + if (connect_sco(dev)) + return FALSE; + + /* + * we try connect to negotiated codec. If it fails, and it isn't + * CVSD codec, try connect CVSD + */ + if (dev->negotiated_codec != CODEC_ID_CVSD) + select_codec(dev, CODEC_ID_CVSD); + + return FALSE; +} + +static void at_cmd_bcc(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_COMMAND: + if (!codec_negotiation_supported(dev)) + break; + + if (hfp_context_has_next(result)) + break; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + /* we haven't negotiated codec, start selection */ + if (!dev->negotiated_codec) { + select_codec(dev, 0); + return; + } + + /* Delay SCO connection so that OK response is send first */ + if (dev->delay_sco == 0) + dev->delay_sco = g_idle_add(connect_sco_delayed, dev); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_SET: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_bcs(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_wbs ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(result, &val)) + break; + + if (hfp_context_has_next(result)) + break; + + /* Remote replied with other codec. Reply with error */ + if (dev->proposed_codec != val) { + dev->proposed_codec = 0; + break; + } + + ev.wbs = val; + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_WBS, sizeof(ev), &ev); + + dev->proposed_codec = 0; + dev->negotiated_codec = val; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + /* + * Delay SCO connection so that OK response is send first, + * then connect with negotiated parameters. + */ + if (dev->delay_sco == 0) + dev->delay_sco = g_idle_add(connect_sco_delayed, dev); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void at_cmd_ckpd(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_hsp_key_press ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(result, &val) || val != 200) + break; + + if (hfp_context_has_next(result)) + break; + + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_HSP_KEY_PRESS, + sizeof(ev), &ev); + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); +} + +static void register_post_slc_at(struct hf_device *dev) +{ + hfp_gw_set_command_handler(dev->gw, at_cmd_unknown, dev, NULL); + + if (dev->hsp) { + hfp_gw_register(dev->gw, at_cmd_ckpd, "+CKPD", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_vgs, "+VGS", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_vgm, "+VGM", dev, NULL); + return; + } + + hfp_gw_register(dev->gw, at_cmd_a, "A", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_d, "D", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_ccwa, "+CCWA", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_chup, "+CHUP", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_clcc, "+CLCC", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_cops, "+COPS", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_cmee, "+CMEE", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_clip, "+CLIP", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_vts, "+VTS", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_cnum, "+CNUM", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bia, "+BIA", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_binp, "+BINP", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bldn, "+BLDN", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bvra, "+BVRA", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_nrec, "+NREC", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_vgs, "+VGS", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_vgm, "+VGM", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bsir, "+BSIR", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_btrh, "+BTRH", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bcc, "+BCC", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bcs, "+BCS", dev, NULL); +} + +static void at_cmd_cmer(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int val; + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + /* mode must be =3 */ + if (!hfp_context_get_number(result, &val) || val != 3) + break; + + /* keyp is don't care */ + if (!hfp_context_get_number(result, &val)) + break; + + /* disp is don't care */ + if (!hfp_context_get_number(result, &val)) + break; + + /* ind must be 0 or 1 */ + if (!hfp_context_get_number(result, &val) || val > 1) + break; + + dev->indicators_enabled = val; + + /* skip bfr if present */ + hfp_context_get_number(result, &val); + + if (hfp_context_has_next(result)) + break; + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + if (dev->features & HFP_HF_FEAT_3WAY) + return; + + register_post_slc_at(dev); + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED); + return; + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) + hfp_gw_disconnect(dev->gw); +} + +static void at_cmd_cind(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_cind ev; + char *buf, *ptr; + int len; + unsigned int i; + + switch (type) { + case HFP_GW_CMD_TYPE_TEST: + + /* + * If device supports Codec Negotiation, AT+BAC should be + * received first + */ + if (codec_negotiation_supported(dev) && + !dev->codecs[CVSD_OFFSET].remote_supported) + break; + + len = strlen("+CIND:") + 1; + + for (i = 0; i < IND_COUNT; i++) { + len += strlen("(\"\",(X,X)),"); + len += strlen(dev->inds[i].name); + } + + buf = g_malloc(len); + + ptr = buf + sprintf(buf, "+CIND:"); + + for (i = 0; i < IND_COUNT; i++) { + ptr += sprintf(ptr, "(\"%s\",(%d%c%d)),", + dev->inds[i].name, + dev->inds[i].min, + dev->inds[i].max == 1 ? ',' : '-', + dev->inds[i].max); + } + + ptr--; + *ptr = '\0'; + + hfp_gw_send_info(dev->gw, "%s", buf); + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + g_free(buf); + return; + case HFP_GW_CMD_TYPE_READ: + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_CIND, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_SET: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) + hfp_gw_disconnect(dev->gw); +} + +static void at_cmd_brsf(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int feat; + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(result, &feat)) + break; + + if (hfp_context_has_next(result)) + break; + + /* TODO verify features */ + dev->features = feat; + + hfp_gw_send_info(dev->gw, "+BRSF: %u", hfp_ag_features); + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) + hfp_gw_disconnect(dev->gw); +} + +static void at_cmd_chld(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + struct hal_ev_handsfree_chld ev; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!hfp_context_get_number(result, &val) || val > 3) + break; + + /* No ECC support */ + if (hfp_context_has_next(result)) + break; + + /* value match HAL type */ + ev.chld = val; + bdaddr2android(&dev->bdaddr, ev.bdaddr); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_EV_HANDSFREE_CHLD, sizeof(ev), &ev); + return; + case HFP_GW_CMD_TYPE_TEST: + hfp_gw_send_info(dev->gw, "+CHLD: (%s)", HFP_AG_CHLD); + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + register_post_slc_at(dev); + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED); + return; + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) + hfp_gw_disconnect(dev->gw); +} + +static struct hfp_codec *find_codec_by_type(struct hf_device *dev, uint8_t type) +{ + int i; + + for (i = 0; i < CODECS_COUNT; i++) + if (type == dev->codecs[i].type) + return &dev->codecs[i]; + + return NULL; +} + +static void at_cmd_bac(struct hfp_context *result, enum hfp_gw_cmd_type type, + void *user_data) +{ + struct hf_device *dev = user_data; + unsigned int val; + + DBG(""); + + switch (type) { + case HFP_GW_CMD_TYPE_SET: + if (!codec_negotiation_supported(dev)) + goto failed; + + /* set codecs to defaults */ + init_codecs(dev); + dev->negotiated_codec = 0; + + /* + * At least CVSD mandatory codec must exist + * HFP V1.6 4.34.1 + */ + if (!hfp_context_get_number(result, &val) || + val != CODEC_ID_CVSD) + goto failed; + + dev->codecs[CVSD_OFFSET].remote_supported = true; + + if (hfp_context_get_number(result, &val)) { + if (val != CODEC_ID_MSBC) + goto failed; + + dev->codecs[MSBC_OFFSET].remote_supported = true; + } + + while (hfp_context_has_next(result)) { + struct hfp_codec *codec; + + if (!hfp_context_get_number(result, &val)) + goto failed; + + codec = find_codec_by_type(dev, val); + if (!codec) + continue; + + codec->remote_supported = true; + } + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + if (dev->proposed_codec) + select_codec(dev, 0); + return; + case HFP_GW_CMD_TYPE_TEST: + case HFP_GW_CMD_TYPE_READ: + case HFP_GW_CMD_TYPE_COMMAND: + break; + } + +failed: + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) + hfp_gw_disconnect(dev->gw); +} + +static void register_slc_at(struct hf_device *dev) +{ + hfp_gw_register(dev->gw, at_cmd_brsf, "+BRSF", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_cind, "+CIND", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_cmer, "+CMER", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_chld, "+CHLD", dev, NULL); + hfp_gw_register(dev->gw, at_cmd_bac, "+BAC", dev, NULL); +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + if (err) { + error("handsfree: connect failed (%s)", err->message); + goto failed; + } + + dev->gw = hfp_gw_new(g_io_channel_unix_get_fd(chan)); + if (!dev->gw) + goto failed; + + g_io_channel_set_close_on_unref(chan, FALSE); + + hfp_gw_set_close_on_unref(dev->gw, true); + hfp_gw_set_disconnect_handler(dev->gw, disconnect_watch, dev, NULL); + + if (dev->hsp) { + register_post_slc_at(dev); + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_CONNECTED); + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED); + return; + } + + register_slc_at(dev); + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_CONNECTED); + return; + +failed: + g_io_channel_shutdown(chan, TRUE, NULL); + device_destroy(dev); +} + +static void confirm_cb(GIOChannel *chan, gpointer data) +{ + char address[18]; + bdaddr_t bdaddr; + GError *err = NULL; + struct hf_device *dev; + + bt_io_get(chan, &err, + BT_IO_OPT_DEST, address, + BT_IO_OPT_DEST_BDADDR, &bdaddr, + BT_IO_OPT_INVALID); + if (err) { + error("handsfree: confirm failed (%s)", err->message); + g_error_free(err); + goto drop; + } + + DBG("incoming connect from %s", address); + + dev = get_device(&bdaddr); + if (!dev) { + error("handsfree: Failed to get device object for %s", address); + goto drop; + } + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED) { + info("handsfree: refusing connection from %s", address); + goto drop; + } + + if (!bt_io_accept(chan, connect_cb, dev, NULL, NULL)) { + error("handsfree: failed to accept connection"); + device_destroy(dev); + goto drop; + } + + dev->hsp = GPOINTER_TO_INT(data); + + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_CONNECTING); + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static void sdp_hsp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct hf_device *dev = data; + sdp_list_t *protos; + GError *gerr = NULL; + GIOChannel *io; + uuid_t class; + int channel; + + DBG(""); + + if (err < 0) { + error("handsfree: unable to get SDP record: %s", + strerror(-err)); + goto fail; + } + + sdp_uuid16_create(&class, HEADSET_SVCLASS_ID); + + /* Find record with proper service class */ + for (; recs; recs = recs->next) { + sdp_record_t *rec = recs->data; + + if (rec && !sdp_uuid_cmp(&rec->svclass, &class)) + break; + } + + if (!recs || !recs->data) { + info("handsfree: no valid HSP SDP records found"); + goto fail; + } + + if (sdp_get_access_protos(recs->data, &protos) < 0) { + error("handsfree: unable to get access protocols from record"); + goto fail; + } + + /* TODO read remote version? */ + /* TODO read volume control support */ + + channel = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + if (channel <= 0) { + error("handsfree: unable to get RFCOMM channel from record"); + goto fail; + } + + io = bt_io_connect(connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_CHANNEL, channel, + BT_IO_OPT_INVALID); + if (!io) { + error("handsfree: unable to connect: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + dev->hsp = true; + + g_io_channel_unref(io); + return; + +fail: + device_destroy(dev); +} + +static int sdp_search_hsp(struct hf_device *dev) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, HEADSET_SVCLASS_ID); + + return bt_search_service(&adapter_addr, &dev->bdaddr, &uuid, + sdp_hsp_search_cb, dev, NULL, 0); +} + +static void sdp_hfp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct hf_device *dev = data; + sdp_list_t *protos; + GError *gerr = NULL; + GIOChannel *io; + uuid_t class; + int channel; + + DBG(""); + + if (err < 0) { + error("handsfree: unable to get SDP record: %s", + strerror(-err)); + goto fail; + } + + sdp_uuid16_create(&class, HANDSFREE_SVCLASS_ID); + + /* Find record with proper service class */ + for (; recs; recs = recs->next) { + sdp_record_t *rec = recs->data; + + if (rec && !sdp_uuid_cmp(&rec->svclass, &class)) + break; + } + + if (!recs || !recs->data) { + info("handsfree: no HFP SDP records found, trying HSP"); + + if (sdp_search_hsp(dev) < 0) { + error("handsfree: HSP SDP search failed"); + goto fail; + } + + return; + } + + if (sdp_get_access_protos(recs->data, &protos) < 0) { + error("handsfree: unable to get access protocols from record"); + goto fail; + } + + channel = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + if (channel <= 0) { + error("handsfree: unable to get RFCOMM channel from record"); + goto fail; + } + + /* TODO read remote version? */ + + io = bt_io_connect(connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_CHANNEL, channel, + BT_IO_OPT_INVALID); + if (!io) { + error("handsfree: unable to connect: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + g_io_channel_unref(io); + return; + +fail: + device_destroy(dev); +} + +static int sdp_search_hfp(struct hf_device *dev) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, HANDSFREE_SVCLASS_ID); + + return bt_search_service(&adapter_addr, &dev->bdaddr, &uuid, + sdp_hfp_search_cb, dev, NULL, 0); +} + +static void handle_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_connect *cmd = buf; + struct hf_device *dev; + char addr[18]; + uint8_t status; + bdaddr_t bdaddr; + int ret; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &bdaddr); + + dev = get_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (dev->state != HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED) { + status = HAL_STATUS_FAILED; + goto failed; + } + + ba2str(&bdaddr, addr); + DBG("connecting to %s", addr); + + /* prefer HFP over HSP */ + ret = hfp_server ? sdp_search_hfp(dev) : sdp_search_hsp(dev); + if (ret < 0) { + error("handsfree: SDP search failed"); + device_destroy(dev); + status = HAL_STATUS_FAILED; + goto failed; + } + + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_CONNECTING); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CONNECT, status); +} + +static void handle_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_disconnect *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (dev->state == HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTED) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (dev->state == HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTING) { + status = HAL_STATUS_SUCCESS; + goto failed; + } + + if (dev->state == HAL_EV_HANDSFREE_CONN_STATE_CONNECTING) { + device_destroy(dev); + } else { + set_state(dev, HAL_EV_HANDSFREE_CONN_STATE_DISCONNECTING); + hfp_gw_disconnect(dev->gw); + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_DISCONNECT, status); +} + +static bool disconnect_sco(struct hf_device *dev) +{ + if (dev->audio_state == HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED || + dev->audio_state == HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTING) + return false; + + bt_sco_disconnect(sco); + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTING); + + return true; +} + +static bool connect_audio(struct hf_device *dev) +{ + if (dev->audio_state != HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED) + return false; + + /* we haven't negotiated codec, start selection */ + if (codec_negotiation_supported(dev) && !dev->negotiated_codec) { + select_codec(dev, 0); + return true; + } + + return connect_sco(dev); +} + +static void handle_connect_audio(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_connect_audio *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->audio_state != HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED) { + status = HAL_STATUS_FAILED; + goto done; + } + + status = connect_audio(dev) ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CONNECT_AUDIO, status); +} + +static void handle_disconnect_audio(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_disconnect_audio *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + status = disconnect_sco(dev) ? HAL_STATUS_SUCCESS : HAL_STATUS_FAILED; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_DISCONNECT_AUDIO, status); +} + +static void handle_start_vr(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_start_vr *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->features & HFP_HF_FEAT_VR) { + hfp_gw_send_info(dev->gw, "+BVRA: 1"); + status = HAL_STATUS_SUCCESS; + } else { + status = HAL_STATUS_FAILED; + } + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_START_VR, status); +} + +static void handle_stop_vr(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_stop_vr *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->features & HFP_HF_FEAT_VR) { + hfp_gw_send_info(dev->gw, "+BVRA: 0"); + status = HAL_STATUS_SUCCESS; + } else { + status = HAL_STATUS_FAILED; + } + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_STOP_VR, status); +} + +static void handle_volume_control(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_volume_control *cmd = buf; + struct hf_device *dev; + uint8_t status, volume; + bdaddr_t bdaddr; + + DBG("type=%u volume=%u", cmd->type, cmd->volume); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + volume = cmd->volume > 15 ? 15 : cmd->volume; + + switch (cmd->type) { + case HAL_HANDSFREE_VOLUME_TYPE_MIC: + hfp_gw_send_info(dev->gw, "+VGM: %u", volume); + + status = HAL_STATUS_SUCCESS; + break; + case HAL_HANDSFREE_VOLUME_TYPE_SPEAKER: + hfp_gw_send_info(dev->gw, "+VGS: %u", volume); + + status = HAL_STATUS_SUCCESS; + break; + default: + status = HAL_STATUS_FAILED; + break; + } + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_VOLUME_CONTROL, status); +} + +static void update_indicator(struct hf_device *dev, int ind, uint8_t val) +{ + DBG("ind=%u new=%u old=%u", ind, val, dev->inds[ind].val); + + if (dev->inds[ind].val == val) + return; + + dev->inds[ind].val = val; + + if (!dev->indicators_enabled) + return; + + if (!dev->inds[ind].active) + return; + + /* indicator numbers in CIEV start from 1 */ + hfp_gw_send_info(dev->gw, "+CIEV: %u,%u", ind + 1, val); +} + +static void device_status_notif(void *data, void *user_data) +{ + struct hf_device *dev = data; + struct hal_cmd_handsfree_device_status_notif *cmd = user_data; + + update_indicator(dev, IND_SERVICE, cmd->state); + update_indicator(dev, IND_ROAM, cmd->type); + update_indicator(dev, IND_SIGNAL, cmd->signal); + update_indicator(dev, IND_BATTCHG, cmd->battery); +} + +static void handle_device_status_notif(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_device_status_notif *cmd = buf; + uint8_t status; + + DBG(""); + + if (queue_isempty(devices)) { + status = HAL_STATUS_FAILED; + goto done; + } + + /* Cast cmd to void as queue api needs that */ + queue_foreach(devices, device_status_notif, (void *) cmd); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF, status); +} + +static void handle_cops(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_cops_response *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + if (len != sizeof(*cmd) + cmd->len || + (cmd->len != 0 && cmd->buf[cmd->len - 1] != '\0')) { + error("Invalid cops response command, terminating"); + raise(SIGTERM); + return; + } + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + hfp_gw_send_info(dev->gw, "+COPS: 0,0,\"%.16s\"", + cmd->len ? (char *) cmd->buf : ""); + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_COPS_RESPONSE, status); +} + +static unsigned int get_callsetup(uint8_t state) +{ + switch (state) { + case HAL_HANDSFREE_CALL_STATE_INCOMING: + return 1; + case HAL_HANDSFREE_CALL_STATE_DIALING: + return 2; + case HAL_HANDSFREE_CALL_STATE_ALERTING: + return 3; + default: + return 0; + } +} + +static void handle_cind(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_cind_response *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + /* HAL doesn't provide indicators values so need to convert here */ + dev->inds[IND_SERVICE].val = cmd->svc; + dev->inds[IND_CALL].val = !!(cmd->num_active + cmd->num_held); + dev->inds[IND_CALLSETUP].val = get_callsetup(cmd->state); + dev->inds[IND_CALLHELD].val = cmd->num_held ? + (cmd->num_active ? 1 : 2) : 0; + dev->inds[IND_SIGNAL].val = cmd->signal; + dev->inds[IND_ROAM].val = cmd->roam; + dev->inds[IND_BATTCHG].val = cmd->batt_chg; + + /* Order must match indicators_defaults table */ + hfp_gw_send_info(dev->gw, "+CIND: %u,%u,%u,%u,%u,%u,%u", + dev->inds[IND_SERVICE].val, + dev->inds[IND_CALL].val, + dev->inds[IND_CALLSETUP].val, + dev->inds[IND_CALLHELD].val, + dev->inds[IND_SIGNAL].val, + dev->inds[IND_ROAM].val, + dev->inds[IND_BATTCHG].val); + + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CIND_RESPONSE, status); +} + +static void handle_formatted_at_resp(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_formatted_at_response *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->len || + (cmd->len != 0 && cmd->buf[cmd->len - 1] != '\0')) { + error("Invalid formatted AT response command, terminating"); + raise(SIGTERM); + return; + } + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + hfp_gw_send_info(dev->gw, "%s", cmd->len ? (char *) cmd->buf : ""); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE, status); +} + +static void handle_at_resp(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_at_response *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (cmd->response == HAL_HANDSFREE_AT_RESPONSE_OK) + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + else if (dev->cmee_enabled) + hfp_gw_send_error(dev->gw, cmd->error); + else + hfp_gw_send_result(dev->gw, HFP_RESULT_ERROR); + + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_AT_RESPONSE, status); +} + +static void handle_clcc_resp(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_clcc_response *cmd = buf; + struct hf_device *dev; + uint8_t status; + bdaddr_t bdaddr; + char *number; + + if (len != sizeof(*cmd) + cmd->number_len || (cmd->number_len != 0 && + cmd->number[cmd->number_len - 1] != '\0')) { + error("Invalid CLCC response command, terminating"); + raise(SIGTERM); + return; + } + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (!cmd->index) { + hfp_gw_send_result(dev->gw, HFP_RESULT_OK); + + status = HAL_STATUS_SUCCESS; + goto done; + } + + number = cmd->number_len ? (char *) cmd->number : ""; + + switch (cmd->state) { + case HAL_HANDSFREE_CALL_STATE_INCOMING: + case HAL_HANDSFREE_CALL_STATE_WAITING: + case HAL_HANDSFREE_CALL_STATE_ACTIVE: + case HAL_HANDSFREE_CALL_STATE_HELD: + case HAL_HANDSFREE_CALL_STATE_DIALING: + case HAL_HANDSFREE_CALL_STATE_ALERTING: + if (cmd->type == HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL && + number[0] != '+') + hfp_gw_send_info(dev->gw, + "+CLCC: %u,%u,%u,%u,%u,\"+%s\",%u", + cmd->index, cmd->dir, cmd->state, + cmd->mode, cmd->mpty, number, + cmd->type); + else + hfp_gw_send_info(dev->gw, + "+CLCC: %u,%u,%u,%u,%u,\"%s\",%u", + cmd->index, cmd->dir, cmd->state, + cmd->mode, cmd->mpty, number, + cmd->type); + + status = HAL_STATUS_SUCCESS; + break; + case HAL_HANDSFREE_CALL_STATE_IDLE: + default: + status = HAL_STATUS_FAILED; + break; + } + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CLCC_RESPONSE, status); +} + +static gboolean ring_cb(gpointer user_data) +{ + struct hf_device *dev = user_data; + + hfp_gw_send_info(dev->gw, "RING"); + + if (dev->clip_enabled && dev->clip) + hfp_gw_send_info(dev->gw, "%s", dev->clip); + + return TRUE; +} + +static void phone_state_dialing(struct hf_device *dev, int num_active, + int num_held) +{ + if (dev->call_hanging_up) { + g_source_remove(dev->call_hanging_up); + dev->call_hanging_up = 0; + } + + update_indicator(dev, IND_CALLSETUP, 2); + + if (num_active == 0 && num_held > 0) + update_indicator(dev, IND_CALLHELD, 2); + + if (dev->num_active == 0 && dev->num_held == 0) + connect_audio(dev); +} + +static void phone_state_alerting(struct hf_device *dev, int num_active, + int num_held) +{ + if (dev->call_hanging_up) { + g_source_remove(dev->call_hanging_up); + dev->call_hanging_up = 0; + } + + update_indicator(dev, IND_CALLSETUP, 3); +} + +static void phone_state_waiting(struct hf_device *dev, int num_active, + int num_held, uint8_t type, + const uint8_t *number, int number_len) +{ + char *num; + + if (!dev->ccwa_enabled) + return; + + num = number_len ? (char *) number : ""; + + if (type == HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL && num[0] != '+') + hfp_gw_send_info(dev->gw, "+CCWA: \"+%s\",%u", num, type); + else + hfp_gw_send_info(dev->gw, "+CCWA: \"%s\",%u", num, type); + + update_indicator(dev, IND_CALLSETUP, 1); +} + +static void phone_state_incoming(struct hf_device *dev, int num_active, + int num_held, uint8_t type, + const uint8_t *number, int number_len) +{ + char *num; + + if (dev->setup_state == HAL_HANDSFREE_CALL_STATE_INCOMING) { + if (dev->num_active != num_active || + dev->num_held != num_held) { + if (dev->num_active == num_held && + dev->num_held == num_active) + return; + /* + * calls changed while waiting call ie. due to + * termination of active call + */ + update_indicator(dev, IND_CALLHELD, + num_held ? (num_active ? 1 : 2) : 0); + update_indicator(dev, IND_CALL, + !!(num_active + num_held)); + } + + return; + } + + if (dev->call_hanging_up) + return; + + if (num_active > 0 || num_held > 0) { + phone_state_waiting(dev, num_active, num_held, type, number, + number_len); + return; + } + + update_indicator(dev, IND_CALLSETUP, 1); + + num = number_len ? (char *) number : ""; + + if (type == HAL_HANDSFREE_CALL_ADDRTYPE_INTERNATIONAL && num[0] != '+') + dev->clip = g_strdup_printf("+CLIP: \"+%s\",%u", num, type); + else + dev->clip = g_strdup_printf("+CLIP: \"%s\",%u", num, type); + + /* send first RING */ + ring_cb(dev); + + dev->ring = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + RING_TIMEOUT, ring_cb, + dev, NULL); + if (!dev->ring) { + g_free(dev->clip); + dev->clip = NULL; + } +} + +static gboolean hang_up_cb(gpointer user_data) +{ + struct hf_device *dev = user_data; + + DBG(""); + + dev->call_hanging_up = 0; + + return FALSE; +} + +static void phone_state_idle(struct hf_device *dev, int num_active, + int num_held) +{ + if (dev->ring) { + g_source_remove(dev->ring); + dev->ring = 0; + + if (dev->clip) { + g_free(dev->clip); + dev->clip = NULL; + } + } + + switch (dev->setup_state) { + case HAL_HANDSFREE_CALL_STATE_INCOMING: + if (num_active > dev->num_active) { + update_indicator(dev, IND_CALL, 1); + + if (dev->num_active == 0 && dev->num_held == 0) + connect_audio(dev); + } + + if (num_held >= dev->num_held && num_held != 0) + update_indicator(dev, IND_CALLHELD, 1); + + update_indicator(dev, IND_CALLSETUP, 0); + + if (num_active == 0 && num_held == 0 && + num_active == dev->num_active && + num_held == dev->num_held) + dev->call_hanging_up = g_timeout_add(800, hang_up_cb, + dev); + break; + case HAL_HANDSFREE_CALL_STATE_DIALING: + case HAL_HANDSFREE_CALL_STATE_ALERTING: + if (num_active > dev->num_active) + update_indicator(dev, IND_CALL, 1); + + update_indicator(dev, IND_CALLHELD, + num_held ? (num_active ? 1 : 2) : 0); + + update_indicator(dev, IND_CALLSETUP, 0); + + /* disconnect SCO if we hang up while dialing or alerting */ + if (num_active == 0 && num_held == 0) + disconnect_sco(dev); + break; + case HAL_HANDSFREE_CALL_STATE_IDLE: + if (dev->call_hanging_up) { + g_source_remove(dev->call_hanging_up); + dev->call_hanging_up = 0; + return; + } + + /* check if calls swapped */ + if (num_held != 0 && num_active != 0 && + dev->num_active == num_held && + dev->num_held == num_active) { + /* TODO better way for forcing indicator */ + dev->inds[IND_CALLHELD].val = 0; + } else if ((num_active > 0 || num_held > 0) && + dev->num_active == 0 && + dev->num_held == 0) { + /* + * If number of active or held calls change but there + * was no call setup change this means that there were + * calls present when headset was connected. + */ + connect_audio(dev); + } else if (num_active == 0 && num_held == 0) { + disconnect_sco(dev); + } + + update_indicator(dev, IND_CALLHELD, + num_held ? (num_active ? 1 : 2) : 0); + update_indicator(dev, IND_CALL, !!(num_active + num_held)); + update_indicator(dev, IND_CALLSETUP, 0); + + /* If call was terminated due to carrier lost send NO CARRIER */ + if (num_active == 0 && num_held == 0 && + dev->inds[IND_SERVICE].val == 0 && + (dev->num_active > 0 || dev->num_held > 0)) + hfp_gw_send_info(dev->gw, "NO CARRIER"); + + break; + default: + DBG("unhandled state %u", dev->setup_state); + break; + } +} + +static void phone_state_change(void *data, void *user_data) +{ + struct hf_device *dev = data; + struct hal_cmd_handsfree_phone_state_change *cmd = user_data; + + switch (cmd->state) { + case HAL_HANDSFREE_CALL_STATE_DIALING: + phone_state_dialing(dev, cmd->num_active, cmd->num_held); + break; + case HAL_HANDSFREE_CALL_STATE_ALERTING: + phone_state_alerting(dev, cmd->num_active, cmd->num_held); + break; + case HAL_HANDSFREE_CALL_STATE_INCOMING: + phone_state_incoming(dev, cmd->num_active, cmd->num_held, + cmd->type, cmd->number, + cmd->number_len); + break; + case HAL_HANDSFREE_CALL_STATE_IDLE: + phone_state_idle(dev, cmd->num_active, cmd->num_held); + break; + default: + DBG("unhandled new state %u (current state %u)", cmd->state, + dev->setup_state); + + return; + } + + dev->num_active = cmd->num_active; + dev->num_held = cmd->num_held; + dev->setup_state = cmd->state; + +} + +static void handle_phone_state_change(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_phone_state_change *cmd = buf; + uint8_t status; + + if (len != sizeof(*cmd) + cmd->number_len || (cmd->number_len != 0 && + cmd->number[cmd->number_len - 1] != '\0')) { + error("Invalid phone state change command, terminating"); + raise(SIGTERM); + return; + } + + DBG("active=%u hold=%u state=%u", cmd->num_active, cmd->num_held, + cmd->state); + + if (queue_isempty(devices)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + /* Cast cmd to void as queue api needs that */ + queue_foreach(devices, phone_state_change, (void *) cmd); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_PHONE_STATE_CHANGE, status); +} + +static void handle_configure_wbs(const void *buf, uint16_t len) +{ + const struct hal_cmd_handsfree_configure_wbs *cmd = buf; + struct hf_device *dev; + bdaddr_t bdaddr; + uint8_t status; + + if (!(hfp_ag_features & HFP_AG_FEAT_CODEC)) { + status = HAL_STATUS_FAILED; + goto done; + } + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev) { + status = HAL_STATUS_FAILED; + goto done; + } + + if (dev->audio_state != HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED) { + status = HAL_STATUS_FAILED; + goto done; + } + + switch (cmd->config) { + case HAL_HANDSFREE_WBS_NO: + dev->codecs[MSBC_OFFSET].local_supported = false; + break; + case HAL_HANDSFREE_WBS_YES: + dev->codecs[MSBC_OFFSET].local_supported = true; + break; + case HAL_HANDSFREE_WBS_NONE: + /* TODO */ + default: + status = HAL_STATUS_FAILED; + goto done; + } + + /* + * cleanup negotiated codec if WBS support was changed, it will be + * renegotiated on next audio connection based on currently supported + * codecs + */ + dev->negotiated_codec = 0; + status = HAL_STATUS_SUCCESS; + +done: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HANDSFREE, + HAL_OP_HANDSFREE_CONFIGURE_WBS, status); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_HANDSFREE_CONNECT */ + { handle_connect, false, + sizeof(struct hal_cmd_handsfree_connect) }, + /* HAL_OP_HANDSFREE_DISCONNECT */ + { handle_disconnect, false, + sizeof(struct hal_cmd_handsfree_disconnect) }, + /* HAL_OP_HANDSFREE_CONNECT_AUDIO */ + { handle_connect_audio, false, + sizeof(struct hal_cmd_handsfree_connect_audio) }, + /* HAL_OP_HANDSFREE_DISCONNECT_AUDIO */ + { handle_disconnect_audio, false, + sizeof(struct hal_cmd_handsfree_disconnect_audio) }, + /* define HAL_OP_HANDSFREE_START_VR */ + { handle_start_vr, false, sizeof(struct hal_cmd_handsfree_start_vr) }, + /* define HAL_OP_HANDSFREE_STOP_VR */ + { handle_stop_vr, false, sizeof(struct hal_cmd_handsfree_stop_vr) }, + /* HAL_OP_HANDSFREE_VOLUME_CONTROL */ + { handle_volume_control, false, + sizeof(struct hal_cmd_handsfree_volume_control) }, + /* HAL_OP_HANDSFREE_DEVICE_STATUS_NOTIF */ + { handle_device_status_notif, false, + sizeof(struct hal_cmd_handsfree_device_status_notif) }, + /* HAL_OP_HANDSFREE_COPS_RESPONSE */ + { handle_cops, true, + sizeof(struct hal_cmd_handsfree_cops_response) }, + /* HAL_OP_HANDSFREE_CIND_RESPONSE */ + { handle_cind, false, + sizeof(struct hal_cmd_handsfree_cind_response) }, + /* HAL_OP_HANDSFREE_FORMATTED_AT_RESPONSE */ + { handle_formatted_at_resp, true, + sizeof(struct hal_cmd_handsfree_formatted_at_response) }, + /* HAL_OP_HANDSFREE_AT_RESPONSE */ + { handle_at_resp, false, + sizeof(struct hal_cmd_handsfree_at_response) }, + /* HAL_OP_HANDSFREE_CLCC_RESPONSE */ + { handle_clcc_resp, true, + sizeof(struct hal_cmd_handsfree_clcc_response) }, + /* HAL_OP_HANDSFREE_PHONE_STATE_CHANGE */ + { handle_phone_state_change, true, + sizeof(struct hal_cmd_handsfree_phone_state_change) }, + /* HAL_OP_HANDSFREE_CONFIGURE_WBS */ + { handle_configure_wbs, false, + sizeof(struct hal_cmd_handsfree_configure_wbs) }, +}; + +static sdp_record_t *headset_ag_record(void) +{ + 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; + uint8_t netid = 0x01; + sdp_data_t *network; + uint8_t ch = HSP_AG_CHANNEL; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + network = sdp_data_alloc(SDP_UINT8, &netid); + if (!network) { + sdp_record_free(record); + return NULL; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &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(NULL, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap_uuid); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(NULL, &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(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Voice Gateway", NULL, NULL); + + sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); + + sdp_data_free(channel); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static bool confirm_sco_cb(const bdaddr_t *addr, uint16_t *voice_settings) +{ + char address[18]; + struct hf_device *dev; + + ba2str(addr, address); + + DBG("incoming SCO connection from %s", address); + + dev = find_device(addr); + if (!dev || dev->state != HAL_EV_HANDSFREE_CONN_STATE_SLC_CONNECTED) { + error("handsfree: audio connection from %s rejected", address); + return false; + } + + /* If HF initiate SCO there must be no WBS used */ + *voice_settings = 0; + + set_audio_state(dev, HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTING); + return true; +} + +static bool enable_hsp_ag(void) +{ + sdp_record_t *rec; + GError *err = NULL; + + DBG(""); + + hsp_server = bt_io_listen(NULL, confirm_cb, GINT_TO_POINTER(true), + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_CHANNEL, HSP_AG_CHANNEL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (!hsp_server) { + error("Failed to listen on Headset rfcomm: %s", err->message); + g_error_free(err); + return false; + } + + rec = headset_ag_record(); + if (!rec) { + error("Failed to allocate Headset record"); + goto failed; + } + + if (bt_adapter_add_record(rec, 0) < 0) { + error("Failed to register Headset record"); + sdp_record_free(rec); + goto failed; + } + + hsp_record_id = rec->handle; + return true; + +failed: + g_io_channel_shutdown(hsp_server, TRUE, NULL); + g_io_channel_unref(hsp_server); + hsp_server = NULL; + + return false; +} + +static void cleanup_hsp_ag(void) +{ + if (hsp_server) { + g_io_channel_shutdown(hsp_server, TRUE, NULL); + g_io_channel_unref(hsp_server); + hsp_server = NULL; + } + + if (hsp_record_id > 0) { + bt_adapter_remove_record(hsp_record_id); + hsp_record_id = 0; + } +} + +static sdp_record_t *hfp_ag_record(void) +{ + 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; + uint8_t ch = HFP_AG_CHANNEL; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + network = sdp_data_alloc(SDP_UINT8, &netid); + if (!network) { + sdp_record_free(record); + return NULL; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &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 = 0x0106; + pfseq = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap_uuid); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(NULL, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + /* Codec Negotiation bit in SDP feature is different then in BRSF */ + sdpfeat = hfp_ag_features & 0x0000003F; + if (hfp_ag_features & HFP_AG_FEAT_CODEC) + sdpfeat |= 0x00000020; + else + sdpfeat &= ~0x00000020; + + features = sdp_data_alloc(SDP_UINT16, &sdpfeat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free Audio Gateway", NULL, NULL); + + sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); + + sdp_data_free(channel); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static bool enable_hfp_ag(void) +{ + sdp_record_t *rec; + GError *err = NULL; + + DBG(""); + + if (hfp_server) + return false; + + hfp_server = bt_io_listen(NULL, confirm_cb, GINT_TO_POINTER(false), + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_CHANNEL, HFP_AG_CHANNEL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (!hfp_server) { + error("Failed to listen on Handsfree rfcomm: %s", err->message); + g_error_free(err); + return false; + } + + rec = hfp_ag_record(); + if (!rec) { + error("Failed to allocate Handsfree record"); + goto failed; + } + + if (bt_adapter_add_record(rec, 0) < 0) { + error("Failed to register Handsfree record"); + sdp_record_free(rec); + goto failed; + } + + hfp_record_id = rec->handle; + return true; + +failed: + g_io_channel_shutdown(hfp_server, TRUE, NULL); + g_io_channel_unref(hfp_server); + hfp_server = NULL; + + return false; +} + +static void cleanup_hfp_ag(void) +{ + if (hfp_server) { + g_io_channel_shutdown(hfp_server, TRUE, NULL); + g_io_channel_unref(hfp_server); + hfp_server = NULL; + } + + if (hfp_record_id > 0) { + bt_adapter_remove_record(hfp_record_id); + hfp_record_id = 0; + } +} + +static void bt_sco_get_fd(const void *buf, uint16_t len) +{ + const struct sco_cmd_get_fd *cmd = buf; + struct sco_rsp_get_fd rsp; + struct hf_device *dev; + bdaddr_t bdaddr; + int fd; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev || !bt_sco_get_fd_and_mtu(sco, &fd, &rsp.mtu)) + goto failed; + + DBG("fd %d mtu %u", fd, rsp.mtu); + + ipc_send_rsp_full(sco_ipc, SCO_SERVICE_ID, SCO_OP_GET_FD, + sizeof(rsp), &rsp, fd); + + return; + +failed: + ipc_send_rsp(sco_ipc, SCO_SERVICE_ID, SCO_OP_STATUS, SCO_STATUS_FAILED); +} + +static const struct ipc_handler sco_handlers[] = { + /* SCO_OP_GET_FD */ + { bt_sco_get_fd, false, sizeof(struct sco_cmd_get_fd) } +}; + +static void bt_sco_unregister(void) +{ + DBG(""); + + ipc_cleanup(sco_ipc); + sco_ipc = NULL; +} + +static bool bt_sco_register(ipc_disconnect_cb disconnect) +{ + DBG(""); + + sco_ipc = ipc_init(BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH), + SCO_SERVICE_ID, false, disconnect, NULL); + if (!sco_ipc) + return false; + + ipc_register(sco_ipc, SCO_SERVICE_ID, sco_handlers, + G_N_ELEMENTS(sco_handlers)); + + return true; +} + +bool bt_handsfree_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode, + int max_clients) +{ + DBG("mode 0x%x max_clients %d", mode, max_clients); + + bacpy(&adapter_addr, addr); + + if (max_clients < 1) + return false; + + devices = queue_new(); + + if (!enable_hsp_ag()) + goto failed_queue; + + sco = bt_sco_new(addr); + if (!sco) + goto failed_hsp; + + bt_sco_set_confirm_cb(sco, confirm_sco_cb); + bt_sco_set_connect_cb(sco, connect_sco_cb); + bt_sco_set_disconnect_cb(sco, disconnect_sco_cb); + + if (mode == HAL_MODE_HANDSFREE_HSP_ONLY) + goto done; + + hfp_ag_features = HFP_AG_FEATURES; + + if (mode == HAL_MODE_HANDSFREE_HFP_WBS) + hfp_ag_features |= HFP_AG_FEAT_CODEC; + + if (enable_hfp_ag()) + goto done; + + bt_sco_unref(sco); + sco = NULL; + hfp_ag_features = 0; +failed_hsp: + cleanup_hsp_ag(); +failed_queue: + queue_destroy(devices, NULL); + devices = NULL; + + return false; + +done: + hal_ipc = ipc; + ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + bt_sco_register(NULL); + + max_hfp_clients = max_clients; + + return true; +} + +void bt_handsfree_unregister(void) +{ + DBG(""); + + bt_sco_unregister(); + ipc_unregister(hal_ipc, HAL_SERVICE_ID_HANDSFREE); + hal_ipc = NULL; + + cleanup_hfp_ag(); + cleanup_hsp_ag(); + bt_sco_unref(sco); + sco = NULL; + + hfp_ag_features = 0; + + queue_destroy(devices, (queue_destroy_func_t) device_destroy); + devices = NULL; + + max_hfp_clients = 0; +}
diff --git a/android/handsfree.h b/android/handsfree.h new file mode 100644 index 0000000..d4fd649 --- /dev/null +++ b/android/handsfree.h
@@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_handsfree_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode, + int max_clients); +void bt_handsfree_unregister(void);
diff --git a/android/hardware/audio.h b/android/hardware/audio.h new file mode 100644 index 0000000..3cc2be5 --- /dev/null +++ b/android/hardware/audio.h
@@ -0,0 +1,667 @@ +/* + * Copyright (C) 2011 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. + */ + + +#ifndef ANDROID_AUDIO_HAL_INTERFACE_H +#define ANDROID_AUDIO_HAL_INTERFACE_H + +#include <stdint.h> +#include <strings.h> +#include <sys/cdefs.h> +#include <sys/types.h> + +#include <hardware/hardware.h> +#include <system/audio.h> +#include <hardware/audio_effect.h> + +__BEGIN_DECLS + +/** + * The id of this module + */ +#define AUDIO_HARDWARE_MODULE_ID "audio" + +/** + * Name of the audio devices to open + */ +#define AUDIO_HARDWARE_INTERFACE "audio_hw_if" + + +/* Use version 0.1 to be compatible with first generation of audio hw module with version_major + * hardcoded to 1. No audio module API change. + */ +#define AUDIO_MODULE_API_VERSION_0_1 HARDWARE_MODULE_API_VERSION(0, 1) +#define AUDIO_MODULE_API_VERSION_CURRENT AUDIO_MODULE_API_VERSION_0_1 + +/* First generation of audio devices had version hardcoded to 0. all devices with versions < 1.0 + * will be considered of first generation API. + */ +#define AUDIO_DEVICE_API_VERSION_0_0 HARDWARE_DEVICE_API_VERSION(0, 0) +#define AUDIO_DEVICE_API_VERSION_1_0 HARDWARE_DEVICE_API_VERSION(1, 0) +#define AUDIO_DEVICE_API_VERSION_2_0 HARDWARE_DEVICE_API_VERSION(2, 0) +#define AUDIO_DEVICE_API_VERSION_3_0 HARDWARE_DEVICE_API_VERSION(3, 0) +#define AUDIO_DEVICE_API_VERSION_CURRENT AUDIO_DEVICE_API_VERSION_3_0 +/* Minimal audio HAL version supported by the audio framework */ +#define AUDIO_DEVICE_API_VERSION_MIN AUDIO_DEVICE_API_VERSION_2_0 + +/** + * List of known audio HAL modules. This is the base name of the audio HAL + * library composed of the "audio." prefix, one of the base names below and + * a suffix specific to the device. + * e.g: audio.primary.goldfish.so or audio.a2dp.default.so + */ + +#define AUDIO_HARDWARE_MODULE_ID_PRIMARY "primary" +#define AUDIO_HARDWARE_MODULE_ID_A2DP "a2dp" +#define AUDIO_HARDWARE_MODULE_ID_USB "usb" +#define AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX "r_submix" +#define AUDIO_HARDWARE_MODULE_ID_CODEC_OFFLOAD "codec_offload" + +/**************************************/ + +/** + * standard audio parameters that the HAL may need to handle + */ + +/** + * audio device parameters + */ + +/* BT SCO Noise Reduction + Echo Cancellation parameters */ +#define AUDIO_PARAMETER_KEY_BT_NREC "bt_headset_nrec" +#define AUDIO_PARAMETER_VALUE_ON "on" +#define AUDIO_PARAMETER_VALUE_OFF "off" + +/* TTY mode selection */ +#define AUDIO_PARAMETER_KEY_TTY_MODE "tty_mode" +#define AUDIO_PARAMETER_VALUE_TTY_OFF "tty_off" +#define AUDIO_PARAMETER_VALUE_TTY_VCO "tty_vco" +#define AUDIO_PARAMETER_VALUE_TTY_HCO "tty_hco" +#define AUDIO_PARAMETER_VALUE_TTY_FULL "tty_full" + +/* Hearing Aid Compatibility - Telecoil (HAC-T) mode on/off + Strings must be in sync with CallFeaturesSetting.java */ +#define AUDIO_PARAMETER_KEY_HAC "HACSetting" +#define AUDIO_PARAMETER_VALUE_HAC_ON "ON" +#define AUDIO_PARAMETER_VALUE_HAC_OFF "OFF" + +/* A2DP sink address set by framework */ +#define AUDIO_PARAMETER_A2DP_SINK_ADDRESS "a2dp_sink_address" + +/* A2DP source address set by framework */ +#define AUDIO_PARAMETER_A2DP_SOURCE_ADDRESS "a2dp_source_address" + +/* Screen state */ +#define AUDIO_PARAMETER_KEY_SCREEN_STATE "screen_state" + +/* Bluetooth SCO wideband */ +#define AUDIO_PARAMETER_KEY_BT_SCO_WB "bt_wbs" + + +/** + * audio stream parameters + */ + +#define AUDIO_PARAMETER_STREAM_ROUTING "routing" /* audio_devices_t */ +#define AUDIO_PARAMETER_STREAM_FORMAT "format" /* audio_format_t */ +#define AUDIO_PARAMETER_STREAM_CHANNELS "channels" /* audio_channel_mask_t */ +#define AUDIO_PARAMETER_STREAM_FRAME_COUNT "frame_count" /* size_t */ +#define AUDIO_PARAMETER_STREAM_INPUT_SOURCE "input_source" /* audio_source_t */ +#define AUDIO_PARAMETER_STREAM_SAMPLING_RATE "sampling_rate" /* uint32_t */ + +#define AUDIO_PARAMETER_DEVICE_DISCONNECT "disconnect" /* audio_devices_t */ + +/* Query supported formats. The response is a '|' separated list of strings from + * audio_format_t enum e.g: "sup_formats=AUDIO_FORMAT_PCM_16_BIT" */ +#define AUDIO_PARAMETER_STREAM_SUP_FORMATS "sup_formats" +/* Query supported channel masks. The response is a '|' separated list of strings from + * audio_channel_mask_t enum e.g: "sup_channels=AUDIO_CHANNEL_OUT_STEREO|AUDIO_CHANNEL_OUT_MONO" */ +#define AUDIO_PARAMETER_STREAM_SUP_CHANNELS "sup_channels" +/* Query supported sampling rates. The response is a '|' separated list of integer values e.g: + * "sup_sampling_rates=44100|48000" */ +#define AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES "sup_sampling_rates" + +/* Get the HW synchronization source used for an output stream. + * Return a valid source (positive integer) or AUDIO_HW_SYNC_INVALID if an error occurs + * or no HW sync source is used. */ +#define AUDIO_PARAMETER_STREAM_HW_AV_SYNC "hw_av_sync" + +/** + * audio codec parameters + */ + +#define AUDIO_OFFLOAD_CODEC_PARAMS "music_offload_codec_param" +#define AUDIO_OFFLOAD_CODEC_BIT_PER_SAMPLE "music_offload_bit_per_sample" +#define AUDIO_OFFLOAD_CODEC_BIT_RATE "music_offload_bit_rate" +#define AUDIO_OFFLOAD_CODEC_AVG_BIT_RATE "music_offload_avg_bit_rate" +#define AUDIO_OFFLOAD_CODEC_ID "music_offload_codec_id" +#define AUDIO_OFFLOAD_CODEC_BLOCK_ALIGN "music_offload_block_align" +#define AUDIO_OFFLOAD_CODEC_SAMPLE_RATE "music_offload_sample_rate" +#define AUDIO_OFFLOAD_CODEC_ENCODE_OPTION "music_offload_encode_option" +#define AUDIO_OFFLOAD_CODEC_NUM_CHANNEL "music_offload_num_channels" +#define AUDIO_OFFLOAD_CODEC_DOWN_SAMPLING "music_offload_down_sampling" +#define AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES "delay_samples" +#define AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES "padding_samples" + +/**************************************/ + +/* common audio stream parameters and operations */ +struct audio_stream { + + /** + * Return the sampling rate in Hz - eg. 44100. + */ + uint32_t (*get_sample_rate)(const struct audio_stream *stream); + + /* currently unused - use set_parameters with key + * AUDIO_PARAMETER_STREAM_SAMPLING_RATE + */ + int (*set_sample_rate)(struct audio_stream *stream, uint32_t rate); + + /** + * Return size of input/output buffer in bytes for this stream - eg. 4800. + * It should be a multiple of the frame size. See also get_input_buffer_size. + */ + size_t (*get_buffer_size)(const struct audio_stream *stream); + + /** + * Return the channel mask - + * e.g. AUDIO_CHANNEL_OUT_STEREO or AUDIO_CHANNEL_IN_STEREO + */ + audio_channel_mask_t (*get_channels)(const struct audio_stream *stream); + + /** + * Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT + */ + audio_format_t (*get_format)(const struct audio_stream *stream); + + /* currently unused - use set_parameters with key + * AUDIO_PARAMETER_STREAM_FORMAT + */ + int (*set_format)(struct audio_stream *stream, audio_format_t format); + + /** + * Put the audio hardware input/output into standby mode. + * Driver should exit from standby mode at the next I/O operation. + * Returns 0 on success and <0 on failure. + */ + int (*standby)(struct audio_stream *stream); + + /** dump the state of the audio input/output device */ + int (*dump)(const struct audio_stream *stream, int fd); + + /** Return the set of device(s) which this stream is connected to */ + audio_devices_t (*get_device)(const struct audio_stream *stream); + + /** + * Currently unused - set_device() corresponds to set_parameters() with key + * AUDIO_PARAMETER_STREAM_ROUTING for both input and output. + * AUDIO_PARAMETER_STREAM_INPUT_SOURCE is an additional information used by + * input streams only. + */ + int (*set_device)(struct audio_stream *stream, audio_devices_t device); + + /** + * set/get audio stream parameters. The function accepts a list of + * parameter key value pairs in the form: key1=value1;key2=value2;... + * + * Some keys are reserved for standard parameters (See AudioParameter class) + * + * If the implementation does not accept a parameter change while + * the output is active but the parameter is acceptable otherwise, it must + * return -ENOSYS. + * + * The audio flinger will put the stream in standby and then change the + * parameter value. + */ + int (*set_parameters)(struct audio_stream *stream, const char *kv_pairs); + + /* + * Returns a pointer to a heap allocated string. The caller is responsible + * for freeing the memory for it using free(). + */ + char * (*get_parameters)(const struct audio_stream *stream, + const char *keys); + int (*add_audio_effect)(const struct audio_stream *stream, + effect_handle_t effect); + int (*remove_audio_effect)(const struct audio_stream *stream, + effect_handle_t effect); +}; +typedef struct audio_stream audio_stream_t; + +/* type of asynchronous write callback events. Mutually exclusive */ +typedef enum { + STREAM_CBK_EVENT_WRITE_READY, /* non blocking write completed */ + STREAM_CBK_EVENT_DRAIN_READY /* drain completed */ +} stream_callback_event_t; + +typedef int (*stream_callback_t)(stream_callback_event_t event, void *param, void *cookie); + +/* type of drain requested to audio_stream_out->drain(). Mutually exclusive */ +typedef enum { + AUDIO_DRAIN_ALL, /* drain() returns when all data has been played */ + AUDIO_DRAIN_EARLY_NOTIFY /* drain() returns a short time before all data + from the current track has been played to + give time for gapless track switch */ +} audio_drain_type_t; + +/** + * audio_stream_out is the abstraction interface for the audio output hardware. + * + * It provides information about various properties of the audio output + * hardware driver. + */ + +struct audio_stream_out { + /** + * Common methods of the audio stream out. This *must* be the first member of audio_stream_out + * as users of this structure will cast a audio_stream to audio_stream_out pointer in contexts + * where it's known the audio_stream references an audio_stream_out. + */ + struct audio_stream common; + + /** + * Return the audio hardware driver estimated latency in milliseconds. + */ + uint32_t (*get_latency)(const struct audio_stream_out *stream); + + /** + * Use this method in situations where audio mixing is done in the + * hardware. This method serves as a direct interface with hardware, + * allowing you to directly set the volume as apposed to via the framework. + * This method might produce multiple PCM outputs or hardware accelerated + * codecs, such as MP3 or AAC. + */ + int (*set_volume)(struct audio_stream_out *stream, float left, float right); + + /** + * Write audio buffer to driver. Returns number of bytes written, or a + * negative status_t. If at least one frame was written successfully prior to the error, + * it is suggested that the driver return that successful (short) byte count + * and then return an error in the subsequent call. + * + * If set_callback() has previously been called to enable non-blocking mode + * the write() is not allowed to block. It must write only the number of + * bytes that currently fit in the driver/hardware buffer and then return + * this byte count. If this is less than the requested write size the + * callback function must be called when more space is available in the + * driver/hardware buffer. + */ + ssize_t (*write)(struct audio_stream_out *stream, const void* buffer, + size_t bytes); + + /* return the number of audio frames written by the audio dsp to DAC since + * the output has exited standby + */ + int (*get_render_position)(const struct audio_stream_out *stream, + uint32_t *dsp_frames); + + /** + * get the local time at which the next write to the audio driver will be presented. + * The units are microseconds, where the epoch is decided by the local audio HAL. + */ + int (*get_next_write_timestamp)(const struct audio_stream_out *stream, + int64_t *timestamp); + + /** + * set the callback function for notifying completion of non-blocking + * write and drain. + * Calling this function implies that all future write() and drain() + * must be non-blocking and use the callback to signal completion. + */ + int (*set_callback)(struct audio_stream_out *stream, + stream_callback_t callback, void *cookie); + + /** + * Notifies to the audio driver to stop playback however the queued buffers are + * retained by the hardware. Useful for implementing pause/resume. Empty implementation + * if not supported however should be implemented for hardware with non-trivial + * latency. In the pause state audio hardware could still be using power. User may + * consider calling suspend after a timeout. + * + * Implementation of this function is mandatory for offloaded playback. + */ + int (*pause)(struct audio_stream_out* stream); + + /** + * Notifies to the audio driver to resume playback following a pause. + * Returns error if called without matching pause. + * + * Implementation of this function is mandatory for offloaded playback. + */ + int (*resume)(struct audio_stream_out* stream); + + /** + * Requests notification when data buffered by the driver/hardware has + * been played. If set_callback() has previously been called to enable + * non-blocking mode, the drain() must not block, instead it should return + * quickly and completion of the drain is notified through the callback. + * If set_callback() has not been called, the drain() must block until + * completion. + * If type==AUDIO_DRAIN_ALL, the drain completes when all previously written + * data has been played. + * If type==AUDIO_DRAIN_EARLY_NOTIFY, the drain completes shortly before all + * data for the current track has played to allow time for the framework + * to perform a gapless track switch. + * + * Drain must return immediately on stop() and flush() call + * + * Implementation of this function is mandatory for offloaded playback. + */ + int (*drain)(struct audio_stream_out* stream, audio_drain_type_t type ); + + /** + * Notifies to the audio driver to flush the queued data. Stream must already + * be paused before calling flush(). + * + * Implementation of this function is mandatory for offloaded playback. + */ + int (*flush)(struct audio_stream_out* stream); + + /** + * Return a recent count of the number of audio frames presented to an external observer. + * This excludes frames which have been written but are still in the pipeline. + * The count is not reset to zero when output enters standby. + * Also returns the value of CLOCK_MONOTONIC as of this presentation count. + * The returned count is expected to be 'recent', + * but does not need to be the most recent possible value. + * However, the associated time should correspond to whatever count is returned. + * Example: assume that N+M frames have been presented, where M is a 'small' number. + * Then it is permissible to return N instead of N+M, + * and the timestamp should correspond to N rather than N+M. + * The terms 'recent' and 'small' are not defined. + * They reflect the quality of the implementation. + * + * 3.0 and higher only. + */ + int (*get_presentation_position)(const struct audio_stream_out *stream, + uint64_t *frames, struct timespec *timestamp); + +}; +typedef struct audio_stream_out audio_stream_out_t; + +struct audio_stream_in { + /** + * Common methods of the audio stream in. This *must* be the first member of audio_stream_in + * as users of this structure will cast a audio_stream to audio_stream_in pointer in contexts + * where it's known the audio_stream references an audio_stream_in. + */ + struct audio_stream common; + + /** set the input gain for the audio driver. This method is for + * for future use */ + int (*set_gain)(struct audio_stream_in *stream, float gain); + + /** Read audio buffer in from audio driver. Returns number of bytes read, or a + * negative status_t. If at least one frame was read prior to the error, + * read should return that byte count and then return an error in the subsequent call. + */ + ssize_t (*read)(struct audio_stream_in *stream, void* buffer, + size_t bytes); + + /** + * Return the amount of input frames lost in the audio driver since the + * last call of this function. + * Audio driver is expected to reset the value to 0 and restart counting + * upon returning the current value by this function call. + * Such loss typically occurs when the user space process is blocked + * longer than the capacity of audio driver buffers. + * + * Unit: the number of input audio frames + */ + uint32_t (*get_input_frames_lost)(struct audio_stream_in *stream); +}; +typedef struct audio_stream_in audio_stream_in_t; + +/** + * return the frame size (number of bytes per sample). + * + * Deprecated: use audio_stream_out_frame_size() or audio_stream_in_frame_size() instead. + */ +__attribute__((__deprecated__)) +static inline size_t audio_stream_frame_size(const struct audio_stream *s) +{ + size_t chan_samp_sz; + audio_format_t format = s->get_format(s); + + if (audio_is_linear_pcm(format)) { + chan_samp_sz = audio_bytes_per_sample(format); + return popcount(s->get_channels(s)) * chan_samp_sz; + } + + return sizeof(int8_t); +} + +/** + * return the frame size (number of bytes per sample) of an output stream. + */ +static inline size_t audio_stream_out_frame_size(const struct audio_stream_out *s) +{ + size_t chan_samp_sz; + audio_format_t format = s->common.get_format(&s->common); + + if (audio_is_linear_pcm(format)) { + chan_samp_sz = audio_bytes_per_sample(format); + return audio_channel_count_from_out_mask(s->common.get_channels(&s->common)) * chan_samp_sz; + } + + return sizeof(int8_t); +} + +/** + * return the frame size (number of bytes per sample) of an input stream. + */ +static inline size_t audio_stream_in_frame_size(const struct audio_stream_in *s) +{ + size_t chan_samp_sz; + audio_format_t format = s->common.get_format(&s->common); + + if (audio_is_linear_pcm(format)) { + chan_samp_sz = audio_bytes_per_sample(format); + return audio_channel_count_from_in_mask(s->common.get_channels(&s->common)) * chan_samp_sz; + } + + return sizeof(int8_t); +} + +/**********************************************************************/ + +/** + * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM + * and the fields of this data structure must begin with hw_module_t + * followed by module specific information. + */ +struct audio_module { + struct hw_module_t common; +}; + +struct audio_hw_device { + /** + * Common methods of the audio device. This *must* be the first member of audio_hw_device + * as users of this structure will cast a hw_device_t to audio_hw_device pointer in contexts + * where it's known the hw_device_t references an audio_hw_device. + */ + struct hw_device_t common; + + /** + * used by audio flinger to enumerate what devices are supported by + * each audio_hw_device implementation. + * + * Return value is a bitmask of 1 or more values of audio_devices_t + * + * NOTE: audio HAL implementations starting with + * AUDIO_DEVICE_API_VERSION_2_0 do not implement this function. + * All supported devices should be listed in audio_policy.conf + * file and the audio policy manager must choose the appropriate + * audio module based on information in this file. + */ + uint32_t (*get_supported_devices)(const struct audio_hw_device *dev); + + /** + * check to see if the audio hardware interface has been initialized. + * returns 0 on success, -ENODEV on failure. + */ + int (*init_check)(const struct audio_hw_device *dev); + + /** set the audio volume of a voice call. Range is between 0.0 and 1.0 */ + int (*set_voice_volume)(struct audio_hw_device *dev, float volume); + + /** + * set the audio volume for all audio activities other than voice call. + * Range between 0.0 and 1.0. If any value other than 0 is returned, + * the software mixer will emulate this capability. + */ + int (*set_master_volume)(struct audio_hw_device *dev, float volume); + + /** + * Get the current master volume value for the HAL, if the HAL supports + * master volume control. AudioFlinger will query this value from the + * primary audio HAL when the service starts and use the value for setting + * the initial master volume across all HALs. HALs which do not support + * this method may leave it set to NULL. + */ + int (*get_master_volume)(struct audio_hw_device *dev, float *volume); + + /** + * set_mode is called when the audio mode changes. AUDIO_MODE_NORMAL mode + * is for standard audio playback, AUDIO_MODE_RINGTONE when a ringtone is + * playing, and AUDIO_MODE_IN_CALL when a call is in progress. + */ + int (*set_mode)(struct audio_hw_device *dev, audio_mode_t mode); + + /* mic mute */ + int (*set_mic_mute)(struct audio_hw_device *dev, bool state); + int (*get_mic_mute)(const struct audio_hw_device *dev, bool *state); + + /* set/get global audio parameters */ + int (*set_parameters)(struct audio_hw_device *dev, const char *kv_pairs); + + /* + * Returns a pointer to a heap allocated string. The caller is responsible + * for freeing the memory for it using free(). + */ + char * (*get_parameters)(const struct audio_hw_device *dev, + const char *keys); + + /* Returns audio input buffer size according to parameters passed or + * 0 if one of the parameters is not supported. + * See also get_buffer_size which is for a particular stream. + */ + size_t (*get_input_buffer_size)(const struct audio_hw_device *dev, + const struct audio_config *config); + + /** This method creates and opens the audio hardware output stream. + * The "address" parameter qualifies the "devices" audio device type if needed. + * The format format depends on the device type: + * - Bluetooth devices use the MAC address of the device in the form "00:11:22:AA:BB:CC" + * - USB devices use the ALSA card and device numbers in the form "card=X;device=Y" + * - Other devices may use a number or any other string. + */ + + int (*open_output_stream)(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out, + const char *address); + + void (*close_output_stream)(struct audio_hw_device *dev, + struct audio_stream_out* stream_out); + + /** This method creates and opens the audio hardware input stream */ + int (*open_input_stream)(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in, + audio_input_flags_t flags, + const char *address, + audio_source_t source); + + void (*close_input_stream)(struct audio_hw_device *dev, + struct audio_stream_in *stream_in); + + /** This method dumps the state of the audio hardware */ + int (*dump)(const struct audio_hw_device *dev, int fd); + + /** + * set the audio mute status for all audio activities. If any value other + * than 0 is returned, the software mixer will emulate this capability. + */ + int (*set_master_mute)(struct audio_hw_device *dev, bool mute); + + /** + * Get the current master mute status for the HAL, if the HAL supports + * master mute control. AudioFlinger will query this value from the primary + * audio HAL when the service starts and use the value for setting the + * initial master mute across all HALs. HALs which do not support this + * method may leave it set to NULL. + */ + int (*get_master_mute)(struct audio_hw_device *dev, bool *mute); + + /** + * Routing control + */ + + /* Creates an audio patch between several source and sink ports. + * The handle is allocated by the HAL and should be unique for this + * audio HAL module. */ + int (*create_audio_patch)(struct audio_hw_device *dev, + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *handle); + + /* Release an audio patch */ + int (*release_audio_patch)(struct audio_hw_device *dev, + audio_patch_handle_t handle); + + /* Fills the list of supported attributes for a given audio port. + * As input, "port" contains the information (type, role, address etc...) + * needed by the HAL to identify the port. + * As output, "port" contains possible attributes (sampling rates, formats, + * channel masks, gain controllers...) for this port. + */ + int (*get_audio_port)(struct audio_hw_device *dev, + struct audio_port *port); + + /* Set audio port configuration */ + int (*set_audio_port_config)(struct audio_hw_device *dev, + const struct audio_port_config *config); + +}; +typedef struct audio_hw_device audio_hw_device_t; + +/** convenience API for opening and closing a supported device */ + +static inline int audio_hw_device_open(const struct hw_module_t* module, + struct audio_hw_device** device) +{ + return module->methods->open(module, AUDIO_HARDWARE_INTERFACE, + (struct hw_device_t**)device); +} + +static inline int audio_hw_device_close(struct audio_hw_device* device) +{ + return device->common.close(&device->common); +} + + +__END_DECLS + +#endif // ANDROID_AUDIO_INTERFACE_H
diff --git a/android/hardware/audio_effect.h b/android/hardware/audio_effect.h new file mode 100644 index 0000000..69ea896 --- /dev/null +++ b/android/hardware/audio_effect.h
@@ -0,0 +1,1010 @@ +/* + * Copyright (C) 2011 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. + */ + + +#ifndef ANDROID_AUDIO_EFFECT_H +#define ANDROID_AUDIO_EFFECT_H + +#include <errno.h> +#include <stdint.h> +#include <strings.h> +#include <sys/cdefs.h> +#include <sys/types.h> + +#include <system/audio.h> + + +__BEGIN_DECLS + + +///////////////////////////////////////////////// +// Common Definitions +///////////////////////////////////////////////// + +// +//--- Effect descriptor structure effect_descriptor_t +// + +// Unique effect ID (can be generated from the following site: +// http://www.itu.int/ITU-T/asn1/uuid.html) +// This format is used for both "type" and "uuid" fields of the effect descriptor structure. +// - When used for effect type and the engine is implementing and effect corresponding to a standard +// OpenSL ES interface, this ID must be the one defined in OpenSLES_IID.h for that interface. +// - When used as uuid, it should be a unique UUID for this particular implementation. +typedef struct effect_uuid_s { + uint32_t timeLow; + uint16_t timeMid; + uint16_t timeHiAndVersion; + uint16_t clockSeq; + uint8_t node[6]; +} effect_uuid_t; + +// Maximum length of character strings in structures defines by this API. +#define EFFECT_STRING_LEN_MAX 64 + +// NULL UUID definition (matches SL_IID_NULL_) +#define EFFECT_UUID_INITIALIZER { 0xec7178ec, 0xe5e1, 0x4432, 0xa3f4, \ + { 0x46, 0x57, 0xe6, 0x79, 0x52, 0x10 } } +static const effect_uuid_t EFFECT_UUID_NULL_ = EFFECT_UUID_INITIALIZER; +static const effect_uuid_t * const EFFECT_UUID_NULL = &EFFECT_UUID_NULL_; +static const char * const EFFECT_UUID_NULL_STR = "ec7178ec-e5e1-4432-a3f4-4657e6795210"; + + +// The effect descriptor contains necessary information to facilitate the enumeration of the effect +// engines present in a library. +typedef struct effect_descriptor_s { + effect_uuid_t type; // UUID of to the OpenSL ES interface implemented by this effect + effect_uuid_t uuid; // UUID for this particular implementation + uint32_t apiVersion; // Version of the effect control API implemented + uint32_t flags; // effect engine capabilities/requirements flags (see below) + uint16_t cpuLoad; // CPU load indication (see below) + uint16_t memoryUsage; // Data Memory usage (see below) + char name[EFFECT_STRING_LEN_MAX]; // human readable effect name + char implementor[EFFECT_STRING_LEN_MAX]; // human readable effect implementor name +} effect_descriptor_t; + +// CPU load and memory usage indication: each effect implementation must provide an indication of +// its CPU and memory usage for the audio effect framework to limit the number of effects +// instantiated at a given time on a given platform. +// The CPU load is expressed in 0.1 MIPS units as estimated on an ARM9E core (ARMv5TE) with 0 WS. +// The memory usage is expressed in KB and includes only dynamically allocated memory + +// Definitions for flags field of effect descriptor. +// +---------------------------+-----------+----------------------------------- +// | description | bits | values +// +---------------------------+-----------+----------------------------------- +// | connection mode | 0..2 | 0 insert: after track process +// | | | 1 auxiliary: connect to track auxiliary +// | | | output and use send level +// | | | 2 replace: replaces track process function; +// | | | must implement SRC, volume and mono to stereo. +// | | | 3 pre processing: applied below audio HAL on input +// | | | 4 post processing: applied below audio HAL on output +// | | | 5 - 7 reserved +// +---------------------------+-----------+----------------------------------- +// | insertion preference | 3..5 | 0 none +// | | | 1 first of the chain +// | | | 2 last of the chain +// | | | 3 exclusive (only effect in the insert chain) +// | | | 4..7 reserved +// +---------------------------+-----------+----------------------------------- +// | Volume management | 6..8 | 0 none +// | | | 1 implements volume control +// | | | 2 requires volume indication +// | | | 4 reserved +// +---------------------------+-----------+----------------------------------- +// | Device indication | 9..11 | 0 none +// | | | 1 requires device updates +// | | | 2, 4 reserved +// +---------------------------+-----------+----------------------------------- +// | Sample input mode | 12..13 | 1 direct: process() function or EFFECT_CMD_SET_CONFIG +// | | | command must specify a buffer descriptor +// | | | 2 provider: process() function uses the +// | | | bufferProvider indicated by the +// | | | EFFECT_CMD_SET_CONFIG command to request input. +// | | | buffers. +// | | | 3 both: both input modes are supported +// +---------------------------+-----------+----------------------------------- +// | Sample output mode | 14..15 | 1 direct: process() function or EFFECT_CMD_SET_CONFIG +// | | | command must specify a buffer descriptor +// | | | 2 provider: process() function uses the +// | | | bufferProvider indicated by the +// | | | EFFECT_CMD_SET_CONFIG command to request output +// | | | buffers. +// | | | 3 both: both output modes are supported +// +---------------------------+-----------+----------------------------------- +// | Hardware acceleration | 16..17 | 0 No hardware acceleration +// | | | 1 non tunneled hw acceleration: the process() function +// | | | reads the samples, send them to HW accelerated +// | | | effect processor, reads back the processed samples +// | | | and returns them to the output buffer. +// | | | 2 tunneled hw acceleration: the process() function is +// | | | transparent. The effect interface is only used to +// | | | control the effect engine. This mode is relevant for +// | | | global effects actually applied by the audio +// | | | hardware on the output stream. +// +---------------------------+-----------+----------------------------------- +// | Audio Mode indication | 18..19 | 0 none +// | | | 1 requires audio mode updates +// | | | 2..3 reserved +// +---------------------------+-----------+----------------------------------- +// | Audio source indication | 20..21 | 0 none +// | | | 1 requires audio source updates +// | | | 2..3 reserved +// +---------------------------+-----------+----------------------------------- +// | Effect offload supported | 22 | 0 The effect cannot be offloaded to an audio DSP +// | | | 1 The effect can be offloaded to an audio DSP +// +---------------------------+-----------+----------------------------------- + +// Insert mode +#define EFFECT_FLAG_TYPE_SHIFT 0 +#define EFFECT_FLAG_TYPE_SIZE 3 +#define EFFECT_FLAG_TYPE_MASK (((1 << EFFECT_FLAG_TYPE_SIZE) -1) \ + << EFFECT_FLAG_TYPE_SHIFT) +#define EFFECT_FLAG_TYPE_INSERT (0 << EFFECT_FLAG_TYPE_SHIFT) +#define EFFECT_FLAG_TYPE_AUXILIARY (1 << EFFECT_FLAG_TYPE_SHIFT) +#define EFFECT_FLAG_TYPE_REPLACE (2 << EFFECT_FLAG_TYPE_SHIFT) +#define EFFECT_FLAG_TYPE_PRE_PROC (3 << EFFECT_FLAG_TYPE_SHIFT) +#define EFFECT_FLAG_TYPE_POST_PROC (4 << EFFECT_FLAG_TYPE_SHIFT) + +// Insert preference +#define EFFECT_FLAG_INSERT_SHIFT (EFFECT_FLAG_TYPE_SHIFT + EFFECT_FLAG_TYPE_SIZE) +#define EFFECT_FLAG_INSERT_SIZE 3 +#define EFFECT_FLAG_INSERT_MASK (((1 << EFFECT_FLAG_INSERT_SIZE) -1) \ + << EFFECT_FLAG_INSERT_SHIFT) +#define EFFECT_FLAG_INSERT_ANY (0 << EFFECT_FLAG_INSERT_SHIFT) +#define EFFECT_FLAG_INSERT_FIRST (1 << EFFECT_FLAG_INSERT_SHIFT) +#define EFFECT_FLAG_INSERT_LAST (2 << EFFECT_FLAG_INSERT_SHIFT) +#define EFFECT_FLAG_INSERT_EXCLUSIVE (3 << EFFECT_FLAG_INSERT_SHIFT) + + +// Volume control +#define EFFECT_FLAG_VOLUME_SHIFT (EFFECT_FLAG_INSERT_SHIFT + EFFECT_FLAG_INSERT_SIZE) +#define EFFECT_FLAG_VOLUME_SIZE 3 +#define EFFECT_FLAG_VOLUME_MASK (((1 << EFFECT_FLAG_VOLUME_SIZE) -1) \ + << EFFECT_FLAG_VOLUME_SHIFT) +#define EFFECT_FLAG_VOLUME_CTRL (1 << EFFECT_FLAG_VOLUME_SHIFT) +#define EFFECT_FLAG_VOLUME_IND (2 << EFFECT_FLAG_VOLUME_SHIFT) +#define EFFECT_FLAG_VOLUME_NONE (0 << EFFECT_FLAG_VOLUME_SHIFT) + +// Device indication +#define EFFECT_FLAG_DEVICE_SHIFT (EFFECT_FLAG_VOLUME_SHIFT + EFFECT_FLAG_VOLUME_SIZE) +#define EFFECT_FLAG_DEVICE_SIZE 3 +#define EFFECT_FLAG_DEVICE_MASK (((1 << EFFECT_FLAG_DEVICE_SIZE) -1) \ + << EFFECT_FLAG_DEVICE_SHIFT) +#define EFFECT_FLAG_DEVICE_IND (1 << EFFECT_FLAG_DEVICE_SHIFT) +#define EFFECT_FLAG_DEVICE_NONE (0 << EFFECT_FLAG_DEVICE_SHIFT) + +// Sample input modes +#define EFFECT_FLAG_INPUT_SHIFT (EFFECT_FLAG_DEVICE_SHIFT + EFFECT_FLAG_DEVICE_SIZE) +#define EFFECT_FLAG_INPUT_SIZE 2 +#define EFFECT_FLAG_INPUT_MASK (((1 << EFFECT_FLAG_INPUT_SIZE) -1) \ + << EFFECT_FLAG_INPUT_SHIFT) +#define EFFECT_FLAG_INPUT_DIRECT (1 << EFFECT_FLAG_INPUT_SHIFT) +#define EFFECT_FLAG_INPUT_PROVIDER (2 << EFFECT_FLAG_INPUT_SHIFT) +#define EFFECT_FLAG_INPUT_BOTH (3 << EFFECT_FLAG_INPUT_SHIFT) + +// Sample output modes +#define EFFECT_FLAG_OUTPUT_SHIFT (EFFECT_FLAG_INPUT_SHIFT + EFFECT_FLAG_INPUT_SIZE) +#define EFFECT_FLAG_OUTPUT_SIZE 2 +#define EFFECT_FLAG_OUTPUT_MASK (((1 << EFFECT_FLAG_OUTPUT_SIZE) -1) \ + << EFFECT_FLAG_OUTPUT_SHIFT) +#define EFFECT_FLAG_OUTPUT_DIRECT (1 << EFFECT_FLAG_OUTPUT_SHIFT) +#define EFFECT_FLAG_OUTPUT_PROVIDER (2 << EFFECT_FLAG_OUTPUT_SHIFT) +#define EFFECT_FLAG_OUTPUT_BOTH (3 << EFFECT_FLAG_OUTPUT_SHIFT) + +// Hardware acceleration mode +#define EFFECT_FLAG_HW_ACC_SHIFT (EFFECT_FLAG_OUTPUT_SHIFT + EFFECT_FLAG_OUTPUT_SIZE) +#define EFFECT_FLAG_HW_ACC_SIZE 2 +#define EFFECT_FLAG_HW_ACC_MASK (((1 << EFFECT_FLAG_HW_ACC_SIZE) -1) \ + << EFFECT_FLAG_HW_ACC_SHIFT) +#define EFFECT_FLAG_HW_ACC_SIMPLE (1 << EFFECT_FLAG_HW_ACC_SHIFT) +#define EFFECT_FLAG_HW_ACC_TUNNEL (2 << EFFECT_FLAG_HW_ACC_SHIFT) + +// Audio mode indication +#define EFFECT_FLAG_AUDIO_MODE_SHIFT (EFFECT_FLAG_HW_ACC_SHIFT + EFFECT_FLAG_HW_ACC_SIZE) +#define EFFECT_FLAG_AUDIO_MODE_SIZE 2 +#define EFFECT_FLAG_AUDIO_MODE_MASK (((1 << EFFECT_FLAG_AUDIO_MODE_SIZE) -1) \ + << EFFECT_FLAG_AUDIO_MODE_SHIFT) +#define EFFECT_FLAG_AUDIO_MODE_IND (1 << EFFECT_FLAG_AUDIO_MODE_SHIFT) +#define EFFECT_FLAG_AUDIO_MODE_NONE (0 << EFFECT_FLAG_AUDIO_MODE_SHIFT) + +// Audio source indication +#define EFFECT_FLAG_AUDIO_SOURCE_SHIFT (EFFECT_FLAG_AUDIO_MODE_SHIFT + EFFECT_FLAG_AUDIO_MODE_SIZE) +#define EFFECT_FLAG_AUDIO_SOURCE_SIZE 2 +#define EFFECT_FLAG_AUDIO_SOURCE_MASK (((1 << EFFECT_FLAG_AUDIO_SOURCE_SIZE) -1) \ + << EFFECT_FLAG_AUDIO_SOURCE_SHIFT) +#define EFFECT_FLAG_AUDIO_SOURCE_IND (1 << EFFECT_FLAG_AUDIO_SOURCE_SHIFT) +#define EFFECT_FLAG_AUDIO_SOURCE_NONE (0 << EFFECT_FLAG_AUDIO_SOURCE_SHIFT) + +// Effect offload indication +#define EFFECT_FLAG_OFFLOAD_SHIFT (EFFECT_FLAG_AUDIO_SOURCE_SHIFT + \ + EFFECT_FLAG_AUDIO_SOURCE_SIZE) +#define EFFECT_FLAG_OFFLOAD_SIZE 1 +#define EFFECT_FLAG_OFFLOAD_MASK (((1 << EFFECT_FLAG_OFFLOAD_SIZE) -1) \ + << EFFECT_FLAG_OFFLOAD_SHIFT) +#define EFFECT_FLAG_OFFLOAD_SUPPORTED (1 << EFFECT_FLAG_OFFLOAD_SHIFT) + +#define EFFECT_MAKE_API_VERSION(M, m) (((M)<<16) | ((m) & 0xFFFF)) +#define EFFECT_API_VERSION_MAJOR(v) ((v)>>16) +#define EFFECT_API_VERSION_MINOR(v) ((m) & 0xFFFF) + + + +///////////////////////////////////////////////// +// Effect control interface +///////////////////////////////////////////////// + +// Effect control interface version 2.0 +#define EFFECT_CONTROL_API_VERSION EFFECT_MAKE_API_VERSION(2,0) + +// Effect control interface structure: effect_interface_s +// The effect control interface is exposed by each effect engine implementation. It consists of +// a set of functions controlling the configuration, activation and process of the engine. +// The functions are grouped in a structure of type effect_interface_s. +// +// Effect control interface handle: effect_handle_t +// The effect_handle_t serves two purposes regarding the implementation of the effect engine: +// - 1 it is the address of a pointer to an effect_interface_s structure where the functions +// of the effect control API for a particular effect are located. +// - 2 it is the address of the context of a particular effect instance. +// A typical implementation in the effect library would define a structure as follows: +// struct effect_module_s { +// const struct effect_interface_s *itfe; +// effect_config_t config; +// effect_context_t context; +// } +// The implementation of EffectCreate() function would then allocate a structure of this +// type and return its address as effect_handle_t +typedef struct effect_interface_s **effect_handle_t; + + +// Forward definition of type audio_buffer_t +typedef struct audio_buffer_s audio_buffer_t; + + + + + + +// Effect control interface definition +struct effect_interface_s { + //////////////////////////////////////////////////////////////////////////////// + // + // Function: process + // + // Description: Effect process function. Takes input samples as specified + // (count and location) in input buffer descriptor and output processed + // samples as specified in output buffer descriptor. If the buffer descriptor + // is not specified the function must use either the buffer or the + // buffer provider function installed by the EFFECT_CMD_SET_CONFIG command. + // The effect framework will call the process() function after the EFFECT_CMD_ENABLE + // command is received and until the EFFECT_CMD_DISABLE is received. When the engine + // receives the EFFECT_CMD_DISABLE command it should turn off the effect gracefully + // and when done indicate that it is OK to stop calling the process() function by + // returning the -ENODATA status. + // + // NOTE: the process() function implementation should be "real-time safe" that is + // it should not perform blocking calls: malloc/free, sleep, read/write/open/close, + // pthread_cond_wait/pthread_mutex_lock... + // + // Input: + // self: handle to the effect interface this function + // is called on. + // inBuffer: buffer descriptor indicating where to read samples to process. + // If NULL, use the configuration passed by EFFECT_CMD_SET_CONFIG command. + // + // outBuffer: buffer descriptor indicating where to write processed samples. + // If NULL, use the configuration passed by EFFECT_CMD_SET_CONFIG command. + // + // Output: + // returned value: 0 successful operation + // -ENODATA the engine has finished the disable phase and the framework + // can stop calling process() + // -EINVAL invalid interface handle or + // invalid input/output buffer description + //////////////////////////////////////////////////////////////////////////////// + int32_t (*process)(effect_handle_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer); + //////////////////////////////////////////////////////////////////////////////// + // + // Function: command + // + // Description: Send a command and receive a response to/from effect engine. + // + // Input: + // self: handle to the effect interface this function + // is called on. + // cmdCode: command code: the command can be a standardized command defined in + // effect_command_e (see below) or a proprietary command. + // cmdSize: size of command in bytes + // pCmdData: pointer to command data + // pReplyData: pointer to reply data + // + // Input/Output: + // replySize: maximum size of reply data as input + // actual size of reply data as output + // + // Output: + // returned value: 0 successful operation + // -EINVAL invalid interface handle or + // invalid command/reply size or format according to command code + // The return code should be restricted to indicate problems related to the this + // API specification. Status related to the execution of a particular command should be + // indicated as part of the reply field. + // + // *pReplyData updated with command response + // + //////////////////////////////////////////////////////////////////////////////// + int32_t (*command)(effect_handle_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); + //////////////////////////////////////////////////////////////////////////////// + // + // Function: get_descriptor + // + // Description: Returns the effect descriptor + // + // Input: + // self: handle to the effect interface this function + // is called on. + // + // Input/Output: + // pDescriptor: address where to return the effect descriptor. + // + // Output: + // returned value: 0 successful operation. + // -EINVAL invalid interface handle or invalid pDescriptor + // *pDescriptor: updated with the effect descriptor. + // + //////////////////////////////////////////////////////////////////////////////// + int32_t (*get_descriptor)(effect_handle_t self, + effect_descriptor_t *pDescriptor); + //////////////////////////////////////////////////////////////////////////////// + // + // Function: process_reverse + // + // Description: Process reverse stream function. This function is used to pass + // a reference stream to the effect engine. If the engine does not need a reference + // stream, this function pointer can be set to NULL. + // This function would typically implemented by an Echo Canceler. + // + // Input: + // self: handle to the effect interface this function + // is called on. + // inBuffer: buffer descriptor indicating where to read samples to process. + // If NULL, use the configuration passed by EFFECT_CMD_SET_CONFIG_REVERSE command. + // + // outBuffer: buffer descriptor indicating where to write processed samples. + // If NULL, use the configuration passed by EFFECT_CMD_SET_CONFIG_REVERSE command. + // If the buffer and buffer provider in the configuration received by + // EFFECT_CMD_SET_CONFIG_REVERSE are also NULL, do not return modified reverse + // stream data + // + // Output: + // returned value: 0 successful operation + // -ENODATA the engine has finished the disable phase and the framework + // can stop calling process_reverse() + // -EINVAL invalid interface handle or + // invalid input/output buffer description + //////////////////////////////////////////////////////////////////////////////// + int32_t (*process_reverse)(effect_handle_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer); +}; + + +// +//--- Standardized command codes for command() function +// +enum effect_command_e { + EFFECT_CMD_INIT, // initialize effect engine + EFFECT_CMD_SET_CONFIG, // configure effect engine (see effect_config_t) + EFFECT_CMD_RESET, // reset effect engine + EFFECT_CMD_ENABLE, // enable effect process + EFFECT_CMD_DISABLE, // disable effect process + EFFECT_CMD_SET_PARAM, // set parameter immediately (see effect_param_t) + EFFECT_CMD_SET_PARAM_DEFERRED, // set parameter deferred + EFFECT_CMD_SET_PARAM_COMMIT, // commit previous set parameter deferred + EFFECT_CMD_GET_PARAM, // get parameter + EFFECT_CMD_SET_DEVICE, // set audio device (see audio.h, audio_devices_t) + EFFECT_CMD_SET_VOLUME, // set volume + EFFECT_CMD_SET_AUDIO_MODE, // set the audio mode (normal, ring, ...) + EFFECT_CMD_SET_CONFIG_REVERSE, // configure effect engine reverse stream(see effect_config_t) + EFFECT_CMD_SET_INPUT_DEVICE, // set capture device (see audio.h, audio_devices_t) + EFFECT_CMD_GET_CONFIG, // read effect engine configuration + EFFECT_CMD_GET_CONFIG_REVERSE, // read configure effect engine reverse stream configuration + EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS,// get all supported configurations for a feature. + EFFECT_CMD_GET_FEATURE_CONFIG, // get current feature configuration + EFFECT_CMD_SET_FEATURE_CONFIG, // set current feature configuration + EFFECT_CMD_SET_AUDIO_SOURCE, // set the audio source (see audio.h, audio_source_t) + EFFECT_CMD_OFFLOAD, // set if effect thread is an offload one, + // send the ioHandle of the effect thread + EFFECT_CMD_FIRST_PROPRIETARY = 0x10000 // first proprietary command code +}; + +//================================================================================================== +// command: EFFECT_CMD_INIT +//-------------------------------------------------------------------------------------------------- +// description: +// Initialize effect engine: All configurations return to default +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_CONFIG +//-------------------------------------------------------------------------------------------------- +// description: +// Apply new audio parameters configurations for input and output buffers +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_config_t) +// data: effect_config_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_RESET +//-------------------------------------------------------------------------------------------------- +// description: +// Reset the effect engine. Keep configuration but resets state and buffer content +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_ENABLE +//-------------------------------------------------------------------------------------------------- +// description: +// Enable the process. Called by the framework before the first call to process() +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_DISABLE +//-------------------------------------------------------------------------------------------------- +// description: +// Disable the process. Called by the framework after the last call to process() +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_PARAM +//-------------------------------------------------------------------------------------------------- +// description: +// Set a parameter and apply it immediately +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_param_t) + size of param and value +// data: effect_param_t + param + value. See effect_param_t definition below for value offset +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_PARAM_DEFERRED +//-------------------------------------------------------------------------------------------------- +// description: +// Set a parameter but apply it only when receiving EFFECT_CMD_SET_PARAM_COMMIT command +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_param_t) + size of param and value +// data: effect_param_t + param + value. See effect_param_t definition below for value offset +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_SET_PARAM_COMMIT +//-------------------------------------------------------------------------------------------------- +// description: +// Apply all previously received EFFECT_CMD_SET_PARAM_DEFERRED commands +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_GET_PARAM +//-------------------------------------------------------------------------------------------------- +// description: +// Get a parameter value +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_param_t) + size of param +// data: effect_param_t + param +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(effect_param_t) + size of param and value +// data: effect_param_t + param + value. See effect_param_t definition below for value offset +//================================================================================================== +// command: EFFECT_CMD_SET_DEVICE +//-------------------------------------------------------------------------------------------------- +// description: +// Set the rendering device the audio output path is connected to. See audio.h, audio_devices_t +// for device values. +// The effect implementation must set EFFECT_FLAG_DEVICE_IND flag in its descriptor to receive this +// command when the device changes +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: uint32_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_SET_VOLUME +//-------------------------------------------------------------------------------------------------- +// description: +// Set and get volume. Used by audio framework to delegate volume control to effect engine. +// The effect implementation must set EFFECT_FLAG_VOLUME_IND or EFFECT_FLAG_VOLUME_CTRL flag in +// its descriptor to receive this command before every call to process() function +// If EFFECT_FLAG_VOLUME_CTRL flag is set in the effect descriptor, the effect engine must return +// the volume that should be applied before the effect is processed. The overall volume (the volume +// actually applied by the effect engine multiplied by the returned value) should match the value +// indicated in the command. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: n * sizeof(uint32_t) +// data: volume for each channel defined in effect_config_t for output buffer expressed in +// 8.24 fixed point format +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: n * sizeof(uint32_t) / 0 +// data: - if EFFECT_FLAG_VOLUME_CTRL is set in effect descriptor: +// volume for each channel defined in effect_config_t for output buffer expressed in +// 8.24 fixed point format +// - if EFFECT_FLAG_VOLUME_CTRL is not set in effect descriptor: +// N/A +// It is legal to receive a null pointer as pReplyData in which case the effect framework has +// delegated volume control to another effect +//================================================================================================== +// command: EFFECT_CMD_SET_AUDIO_MODE +//-------------------------------------------------------------------------------------------------- +// description: +// Set the audio mode. The effect implementation must set EFFECT_FLAG_AUDIO_MODE_IND flag in its +// descriptor to receive this command when the audio mode changes. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: audio_mode_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_SET_CONFIG_REVERSE +//-------------------------------------------------------------------------------------------------- +// description: +// Apply new audio parameters configurations for input and output buffers of reverse stream. +// An example of reverse stream is the echo reference supplied to an Acoustic Echo Canceler. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_config_t) +// data: effect_config_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_INPUT_DEVICE +//-------------------------------------------------------------------------------------------------- +// description: +// Set the capture device the audio input path is connected to. See audio.h, audio_devices_t +// for device values. +// The effect implementation must set EFFECT_FLAG_DEVICE_IND flag in its descriptor to receive this +// command when the device changes +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: uint32_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_GET_CONFIG +//-------------------------------------------------------------------------------------------------- +// description: +// Read audio parameters configurations for input and output buffers +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(effect_config_t) +// data: effect_config_t +//================================================================================================== +// command: EFFECT_CMD_GET_CONFIG_REVERSE +//-------------------------------------------------------------------------------------------------- +// description: +// Read audio parameters configurations for input and output buffers of reverse stream +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(effect_config_t) +// data: effect_config_t +//================================================================================================== +// command: EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS +//-------------------------------------------------------------------------------------------------- +// description: +// Queries for supported configurations for a particular feature (e.g. get the supported +// combinations of main and auxiliary channels for a noise suppressor). +// The command parameter is the feature identifier (See effect_feature_e for a list of defined +// features) followed by the maximum number of configuration descriptor to return. +// The reply is composed of: +// - status (uint32_t): +// - 0 if feature is supported +// - -ENOSYS if the feature is not supported, +// - -ENOMEM if the feature is supported but the total number of supported configurations +// exceeds the maximum number indicated by the caller. +// - total number of supported configurations (uint32_t) +// - an array of configuration descriptors. +// The actual number of descriptors returned must not exceed the maximum number indicated by +// the caller. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 2 x sizeof(uint32_t) +// data: effect_feature_e + maximum number of configurations to return +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 2 x sizeof(uint32_t) + n x sizeof (<config descriptor>) +// data: status + total number of configurations supported + array of n config descriptors +//================================================================================================== +// command: EFFECT_CMD_GET_FEATURE_CONFIG +//-------------------------------------------------------------------------------------------------- +// description: +// Retrieves current configuration for a given feature. +// The reply status is: +// - 0 if feature is supported +// - -ENOSYS if the feature is not supported, +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: effect_feature_e +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(uint32_t) + sizeof (<config descriptor>) +// data: status + config descriptor +//================================================================================================== +// command: EFFECT_CMD_SET_FEATURE_CONFIG +//-------------------------------------------------------------------------------------------------- +// description: +// Sets current configuration for a given feature. +// The reply status is: +// - 0 if feature is supported +// - -ENOSYS if the feature is not supported, +// - -EINVAL if the configuration is invalid +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) + sizeof (<config descriptor>) +// data: effect_feature_e + config descriptor +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(uint32_t) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_AUDIO_SOURCE +//-------------------------------------------------------------------------------------------------- +// description: +// Set the audio source the capture path is configured for (Camcorder, voice recognition...). +// See audio.h, audio_source_t for values. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: uint32_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_OFFLOAD +//-------------------------------------------------------------------------------------------------- +// description: +// 1.indicate if the playback thread the effect is attached to is offloaded or not +// 2.update the io handle of the playback thread the effect is attached to +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_offload_param_t) +// data: effect_offload_param_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(uint32_t) +// data: uint32_t +//-------------------------------------------------------------------------------------------------- +// command: EFFECT_CMD_FIRST_PROPRIETARY +//-------------------------------------------------------------------------------------------------- +// description: +// All proprietary effect commands must use command codes above this value. The size and format of +// command and response fields is free in this case +//================================================================================================== + + +// Audio buffer descriptor used by process(), bufferProvider() functions and buffer_config_t +// structure. Multi-channel audio is always interleaved. The channel order is from LSB to MSB with +// regard to the channel mask definition in audio.h, audio_channel_mask_t e.g : +// Stereo: left, right +// 5 point 1: front left, front right, front center, low frequency, back left, back right +// The buffer size is expressed in frame count, a frame being composed of samples for all +// channels at a given time. Frame size for unspecified format (AUDIO_FORMAT_OTHER) is 8 bit by +// definition +struct audio_buffer_s { + size_t frameCount; // number of frames in buffer + union { + void* raw; // raw pointer to start of buffer + int32_t* s32; // pointer to signed 32 bit data at start of buffer + int16_t* s16; // pointer to signed 16 bit data at start of buffer + uint8_t* u8; // pointer to unsigned 8 bit data at start of buffer + }; +}; + +// The buffer_provider_s structure contains functions that can be used +// by the effect engine process() function to query and release input +// or output audio buffer. +// The getBuffer() function is called to retrieve a buffer where data +// should read from or written to by process() function. +// The releaseBuffer() function MUST be called when the buffer retrieved +// with getBuffer() is not needed anymore. +// The process function should use the buffer provider mechanism to retrieve +// input or output buffer if the inBuffer or outBuffer passed as argument is NULL +// and the buffer configuration (buffer_config_t) given by the EFFECT_CMD_SET_CONFIG +// command did not specify an audio buffer. + +typedef int32_t (* buffer_function_t)(void *cookie, audio_buffer_t *buffer); + +typedef struct buffer_provider_s { + buffer_function_t getBuffer; // retrieve next buffer + buffer_function_t releaseBuffer; // release used buffer + void *cookie; // for use by client of buffer provider functions +} buffer_provider_t; + + +// The buffer_config_s structure specifies the input or output audio format +// to be used by the effect engine. It is part of the effect_config_t +// structure that defines both input and output buffer configurations and is +// passed by the EFFECT_CMD_SET_CONFIG or EFFECT_CMD_SET_CONFIG_REVERSE command. +typedef struct buffer_config_s { + audio_buffer_t buffer; // buffer for use by process() function if not passed explicitly + uint32_t samplingRate; // sampling rate + uint32_t channels; // channel mask (see audio_channel_mask_t in audio.h) + buffer_provider_t bufferProvider; // buffer provider + uint8_t format; // Audio format (see audio_format_t in audio.h) + uint8_t accessMode; // read/write or accumulate in buffer (effect_buffer_access_e) + uint16_t mask; // indicates which of the above fields is valid +} buffer_config_t; + +// Values for "accessMode" field of buffer_config_t: +// overwrite, read only, accumulate (read/modify/write) +enum effect_buffer_access_e { + EFFECT_BUFFER_ACCESS_WRITE, + EFFECT_BUFFER_ACCESS_READ, + EFFECT_BUFFER_ACCESS_ACCUMULATE + +}; + +// feature identifiers for EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS command +enum effect_feature_e { + EFFECT_FEATURE_AUX_CHANNELS, // supports auxiliary channels (e.g. dual mic noise suppressor) + EFFECT_FEATURE_CNT +}; + +// EFFECT_FEATURE_AUX_CHANNELS feature configuration descriptor. Describe a combination +// of main and auxiliary channels supported +typedef struct channel_config_s { + audio_channel_mask_t main_channels; // channel mask for main channels + audio_channel_mask_t aux_channels; // channel mask for auxiliary channels +} channel_config_t; + + +// Values for bit field "mask" in buffer_config_t. If a bit is set, the corresponding field +// in buffer_config_t must be taken into account when executing the EFFECT_CMD_SET_CONFIG command +#define EFFECT_CONFIG_BUFFER 0x0001 // buffer field must be taken into account +#define EFFECT_CONFIG_SMP_RATE 0x0002 // samplingRate field must be taken into account +#define EFFECT_CONFIG_CHANNELS 0x0004 // channels field must be taken into account +#define EFFECT_CONFIG_FORMAT 0x0008 // format field must be taken into account +#define EFFECT_CONFIG_ACC_MODE 0x0010 // accessMode field must be taken into account +#define EFFECT_CONFIG_PROVIDER 0x0020 // bufferProvider field must be taken into account +#define EFFECT_CONFIG_ALL (EFFECT_CONFIG_BUFFER | EFFECT_CONFIG_SMP_RATE | \ + EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT | \ + EFFECT_CONFIG_ACC_MODE | EFFECT_CONFIG_PROVIDER) + + +// effect_config_s structure describes the format of the pCmdData argument of EFFECT_CMD_SET_CONFIG +// command to configure audio parameters and buffers for effect engine input and output. +typedef struct effect_config_s { + buffer_config_t inputCfg; + buffer_config_t outputCfg; +} effect_config_t; + + +// effect_param_s structure describes the format of the pCmdData argument of EFFECT_CMD_SET_PARAM +// command and pCmdData and pReplyData of EFFECT_CMD_GET_PARAM command. +// psize and vsize represent the actual size of parameter and value. +// +// NOTE: the start of value field inside the data field is always on a 32 bit boundary: +// +// +-----------+ +// | status | sizeof(int) +// +-----------+ +// | psize | sizeof(int) +// +-----------+ +// | vsize | sizeof(int) +// +-----------+ +// | | | | +// ~ parameter ~ > psize | +// | | | > ((psize - 1)/sizeof(int) + 1) * sizeof(int) +// +-----------+ | +// | padding | | +// +-----------+ +// | | | +// ~ value ~ > vsize +// | | | +// +-----------+ + +typedef struct effect_param_s { + int32_t status; // Transaction status (unused for command, used for reply) + uint32_t psize; // Parameter size + uint32_t vsize; // Value size + char data[]; // Start of Parameter + Value data +} effect_param_t; + +// structure used by EFFECT_CMD_OFFLOAD command +typedef struct effect_offload_param_s { + bool isOffload; // true if the playback thread the effect is attached to is offloaded + int ioHandle; // io handle of the playback thread the effect is attached to +} effect_offload_param_t; + + +///////////////////////////////////////////////// +// Effect library interface +///////////////////////////////////////////////// + +// Effect library interface version 3.0 +// Note that EffectsFactory.c only checks the major version component, so changes to the minor +// number can only be used for fully backwards compatible changes +#define EFFECT_LIBRARY_API_VERSION EFFECT_MAKE_API_VERSION(3,0) + +#define AUDIO_EFFECT_LIBRARY_TAG ((('A') << 24) | (('E') << 16) | (('L') << 8) | ('T')) + +// Every effect library must have a data structure named AUDIO_EFFECT_LIBRARY_INFO_SYM +// and the fields of this data structure must begin with audio_effect_library_t + +typedef struct audio_effect_library_s { + // tag must be initialized to AUDIO_EFFECT_LIBRARY_TAG + uint32_t tag; + // Version of the effect library API : 0xMMMMmmmm MMMM: Major, mmmm: minor + uint32_t version; + // Name of this library + const char *name; + // Author/owner/implementor of the library + const char *implementor; + + //////////////////////////////////////////////////////////////////////////////// + // + // Function: create_effect + // + // Description: Creates an effect engine of the specified implementation uuid and + // returns an effect control interface on this engine. The function will allocate the + // resources for an instance of the requested effect engine and return + // a handle on the effect control interface. + // + // Input: + // uuid: pointer to the effect uuid. + // sessionId: audio session to which this effect instance will be attached. All effects + // created with the same session ID are connected in series and process the same signal + // stream. Knowing that two effects are part of the same effect chain can help the + // library implement some kind of optimizations. + // ioId: identifies the output or input stream this effect is directed to at audio HAL. + // For future use especially with tunneled HW accelerated effects + // + // Input/Output: + // pHandle: address where to return the effect interface handle. + // + // Output: + // returned value: 0 successful operation. + // -ENODEV library failed to initialize + // -EINVAL invalid pEffectUuid or pHandle + // -ENOENT no effect with this uuid found + // *pHandle: updated with the effect interface handle. + // + //////////////////////////////////////////////////////////////////////////////// + int32_t (*create_effect)(const effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_handle_t *pHandle); + + //////////////////////////////////////////////////////////////////////////////// + // + // Function: release_effect + // + // Description: Releases the effect engine whose handle is given as argument. + // All resources allocated to this particular instance of the effect are + // released. + // + // Input: + // handle: handle on the effect interface to be released. + // + // Output: + // returned value: 0 successful operation. + // -ENODEV library failed to initialize + // -EINVAL invalid interface handle + // + //////////////////////////////////////////////////////////////////////////////// + int32_t (*release_effect)(effect_handle_t handle); + + //////////////////////////////////////////////////////////////////////////////// + // + // Function: get_descriptor + // + // Description: Returns the descriptor of the effect engine which implementation UUID is + // given as argument. + // + // Input/Output: + // uuid: pointer to the effect uuid. + // pDescriptor: address where to return the effect descriptor. + // + // Output: + // returned value: 0 successful operation. + // -ENODEV library failed to initialize + // -EINVAL invalid pDescriptor or uuid + // *pDescriptor: updated with the effect descriptor. + // + //////////////////////////////////////////////////////////////////////////////// + int32_t (*get_descriptor)(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptor); +} audio_effect_library_t; + +// Name of the hal_module_info +#define AUDIO_EFFECT_LIBRARY_INFO_SYM AELI + +// Name of the hal_module_info as a string +#define AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR "AELI" + +__END_DECLS + +#endif // ANDROID_AUDIO_EFFECT_H
diff --git a/android/hardware/bluetooth.h b/android/hardware/bluetooth.h new file mode 100644 index 0000000..74cd1fc --- /dev/null +++ b/android/hardware/bluetooth.h
@@ -0,0 +1,550 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BLUETOOTH_H +#define ANDROID_INCLUDE_BLUETOOTH_H + +#include <stdbool.h> +#include <stdint.h> +#include <sys/cdefs.h> +#include <sys/types.h> + +#include <hardware/hardware.h> + +__BEGIN_DECLS + +/** + * The Bluetooth Hardware Module ID + */ + +#define BT_HARDWARE_MODULE_ID "bluetooth" +#define BT_STACK_MODULE_ID "bluetooth" +#define BT_STACK_TEST_MODULE_ID "bluetooth_test" + + +/* Bluetooth profile interface IDs */ + +#define BT_PROFILE_HANDSFREE_ID "handsfree" +#define BT_PROFILE_HANDSFREE_CLIENT_ID "handsfree_client" +#define BT_PROFILE_ADVANCED_AUDIO_ID "a2dp" +#define BT_PROFILE_ADVANCED_AUDIO_SINK_ID "a2dp_sink" +#define BT_PROFILE_HEALTH_ID "health" +#define BT_PROFILE_SOCKETS_ID "socket" +#define BT_PROFILE_HIDHOST_ID "hidhost" +#define BT_PROFILE_PAN_ID "pan" +#define BT_PROFILE_MAP_CLIENT_ID "map_client" + +#define BT_PROFILE_GATT_ID "gatt" +#define BT_PROFILE_AV_RC_ID "avrcp" +#define BT_PROFILE_AV_RC_CTRL_ID "avrcp_ctrl" + +/** Bluetooth Address */ +typedef struct { + uint8_t address[6]; +} __attribute__((packed))bt_bdaddr_t; + +/** Bluetooth Device Name */ +typedef struct { + uint8_t name[249]; +} __attribute__((packed))bt_bdname_t; + +/** Bluetooth Adapter Visibility Modes*/ +typedef enum { + BT_SCAN_MODE_NONE, + BT_SCAN_MODE_CONNECTABLE, + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE +} bt_scan_mode_t; + +/** Bluetooth Adapter State */ +typedef enum { + BT_STATE_OFF, + BT_STATE_ON +} bt_state_t; + +/** Bluetooth Error Status */ +/** We need to build on this */ + +typedef enum { + BT_STATUS_SUCCESS, + BT_STATUS_FAIL, + BT_STATUS_NOT_READY, + BT_STATUS_NOMEM, + BT_STATUS_BUSY, + BT_STATUS_DONE, /* request already completed */ + BT_STATUS_UNSUPPORTED, + BT_STATUS_PARM_INVALID, + BT_STATUS_UNHANDLED, + BT_STATUS_AUTH_FAILURE, + BT_STATUS_RMT_DEV_DOWN, + BT_STATUS_AUTH_REJECTED + +} bt_status_t; + +/** Bluetooth PinKey Code */ +typedef struct { + uint8_t pin[16]; +} __attribute__((packed))bt_pin_code_t; + +typedef struct { + uint8_t status; + uint8_t ctrl_state; /* stack reported state */ + uint64_t tx_time; /* in ms */ + uint64_t rx_time; /* in ms */ + uint64_t idle_time; /* in ms */ + uint64_t energy_used; /* a product of mA, V and ms */ +} __attribute__((packed))bt_activity_energy_info; + +/** Bluetooth Adapter Discovery state */ +typedef enum { + BT_DISCOVERY_STOPPED, + BT_DISCOVERY_STARTED +} bt_discovery_state_t; + +/** Bluetooth ACL connection state */ +typedef enum { + BT_ACL_STATE_CONNECTED, + BT_ACL_STATE_DISCONNECTED +} bt_acl_state_t; + +/** Bluetooth 128-bit UUID */ +typedef struct { + uint8_t uu[16]; +} bt_uuid_t; + +/** Bluetooth SDP service record */ +typedef struct +{ + bt_uuid_t uuid; + uint16_t channel; + char name[256]; // what's the maximum length +} bt_service_record_t; + + +/** Bluetooth Remote Version info */ +typedef struct +{ + int version; + int sub_ver; + int manufacturer; +} bt_remote_version_t; + +typedef struct +{ + uint8_t local_privacy_enabled; + uint8_t max_adv_instance; + uint8_t rpa_offload_supported; + uint8_t max_irk_list_size; + uint8_t max_adv_filter_supported; + uint8_t scan_result_storage_size_lobyte; + uint8_t scan_result_storage_size_hibyte; + uint8_t activity_energy_info_supported; +}bt_local_le_features_t; + +/* Bluetooth Adapter and Remote Device property types */ +typedef enum { + /* Properties common to both adapter and remote device */ + /** + * Description - Bluetooth Device Name + * Access mode - Adapter name can be GET/SET. Remote device can be GET + * Data type - bt_bdname_t + */ + BT_PROPERTY_BDNAME = 0x1, + /** + * Description - Bluetooth Device Address + * Access mode - Only GET. + * Data type - bt_bdaddr_t + */ + BT_PROPERTY_BDADDR, + /** + * Description - Bluetooth Service 128-bit UUIDs + * Access mode - Only GET. + * Data type - Array of bt_uuid_t (Array size inferred from property length). + */ + BT_PROPERTY_UUIDS, + /** + * Description - Bluetooth Class of Device as found in Assigned Numbers + * Access mode - Only GET. + * Data type - uint32_t. + */ + BT_PROPERTY_CLASS_OF_DEVICE, + /** + * Description - Device Type - BREDR, BLE or DUAL Mode + * Access mode - Only GET. + * Data type - bt_device_type_t + */ + BT_PROPERTY_TYPE_OF_DEVICE, + /** + * Description - Bluetooth Service Record + * Access mode - Only GET. + * Data type - bt_service_record_t + */ + BT_PROPERTY_SERVICE_RECORD, + + /* Properties unique to adapter */ + /** + * Description - Bluetooth Adapter scan mode + * Access mode - GET and SET + * Data type - bt_scan_mode_t. + */ + BT_PROPERTY_ADAPTER_SCAN_MODE, + /** + * Description - List of bonded devices + * Access mode - Only GET. + * Data type - Array of bt_bdaddr_t of the bonded remote devices + * (Array size inferred from property length). + */ + BT_PROPERTY_ADAPTER_BONDED_DEVICES, + /** + * Description - Bluetooth Adapter Discovery timeout (in seconds) + * Access mode - GET and SET + * Data type - uint32_t + */ + BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, + + /* Properties unique to remote device */ + /** + * Description - User defined friendly name of the remote device + * Access mode - GET and SET + * Data type - bt_bdname_t. + */ + BT_PROPERTY_REMOTE_FRIENDLY_NAME, + /** + * Description - RSSI value of the inquired remote device + * Access mode - Only GET. + * Data type - int32_t. + */ + BT_PROPERTY_REMOTE_RSSI, + /** + * Description - Remote version info + * Access mode - SET/GET. + * Data type - bt_remote_version_t. + */ + + BT_PROPERTY_REMOTE_VERSION_INFO, + + /** + * Description - Local LE features + * Access mode - GET. + * Data type - bt_local_le_features_t. + */ + BT_PROPERTY_LOCAL_LE_FEATURES, + + BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP = 0xFF, +} bt_property_type_t; + +/** Bluetooth Adapter Property data structure */ +typedef struct +{ + bt_property_type_t type; + int len; + void *val; +} bt_property_t; + + +/** Bluetooth Device Type */ +typedef enum { + BT_DEVICE_DEVTYPE_BREDR = 0x1, + BT_DEVICE_DEVTYPE_BLE, + BT_DEVICE_DEVTYPE_DUAL +} bt_device_type_t; +/** Bluetooth Bond state */ +typedef enum { + BT_BOND_STATE_NONE, + BT_BOND_STATE_BONDING, + BT_BOND_STATE_BONDED +} bt_bond_state_t; + +/** Bluetooth SSP Bonding Variant */ +typedef enum { + BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + BT_SSP_VARIANT_PASSKEY_ENTRY, + BT_SSP_VARIANT_CONSENT, + BT_SSP_VARIANT_PASSKEY_NOTIFICATION +} bt_ssp_variant_t; + +#define BT_MAX_NUM_UUIDS 32 + +/** Bluetooth Interface callbacks */ + +/** Bluetooth Enable/Disable Callback. */ +typedef void (*adapter_state_changed_callback)(bt_state_t state); + +/** GET/SET Adapter Properties callback */ +/* TODO: For the GET/SET property APIs/callbacks, we may need a session + * identifier to associate the call with the callback. This would be needed + * whenever more than one simultaneous instance of the same adapter_type + * is get/set. + * + * If this is going to be handled in the Java framework, then we do not need + * to manage sessions here. + */ +typedef void (*adapter_properties_callback)(bt_status_t status, + int num_properties, + bt_property_t *properties); + +/** GET/SET Remote Device Properties callback */ +/** TODO: For remote device properties, do not see a need to get/set + * multiple properties - num_properties shall be 1 + */ +typedef void (*remote_device_properties_callback)(bt_status_t status, + bt_bdaddr_t *bd_addr, + int num_properties, + bt_property_t *properties); + +/** New device discovered callback */ +/** If EIR data is not present, then BD_NAME and RSSI shall be NULL and -1 + * respectively */ +typedef void (*device_found_callback)(int num_properties, + bt_property_t *properties); + +/** Discovery state changed callback */ +typedef void (*discovery_state_changed_callback)(bt_discovery_state_t state); + +/** Bluetooth Legacy PinKey Request callback */ +typedef void (*pin_request_callback)(bt_bdaddr_t *remote_bd_addr, + bt_bdname_t *bd_name, uint32_t cod); + +/** Bluetooth SSP Request callback - Just Works & Numeric Comparison*/ +/** pass_key - Shall be 0 for BT_SSP_PAIRING_VARIANT_CONSENT & + * BT_SSP_PAIRING_PASSKEY_ENTRY */ +/* TODO: Passkey request callback shall not be needed for devices with display + * capability. We still need support this in the stack for completeness */ +typedef void (*ssp_request_callback)(bt_bdaddr_t *remote_bd_addr, + bt_bdname_t *bd_name, + uint32_t cod, + bt_ssp_variant_t pairing_variant, + uint32_t pass_key); + +/** Bluetooth Bond state changed callback */ +/* Invoked in response to create_bond, cancel_bond or remove_bond */ +typedef void (*bond_state_changed_callback)(bt_status_t status, + bt_bdaddr_t *remote_bd_addr, + bt_bond_state_t state); + +/** Bluetooth ACL connection state changed callback */ +typedef void (*acl_state_changed_callback)(bt_status_t status, bt_bdaddr_t *remote_bd_addr, + bt_acl_state_t state); + +typedef enum { + ASSOCIATE_JVM, + DISASSOCIATE_JVM +} bt_cb_thread_evt; + +/** Thread Associate/Disassociate JVM Callback */ +/* Callback that is invoked by the callback thread to allow upper layer to attach/detach to/from + * the JVM */ +typedef void (*callback_thread_event)(bt_cb_thread_evt evt); + +/** Bluetooth Test Mode Callback */ +/* Receive any HCI event from controller. Must be in DUT Mode for this callback to be received */ +typedef void (*dut_mode_recv_callback)(uint16_t opcode, uint8_t *buf, uint8_t len); + +/* LE Test mode callbacks +* This callback shall be invoked whenever the le_tx_test, le_rx_test or le_test_end is invoked +* The num_packets is valid only for le_test_end command */ +typedef void (*le_test_mode_callback)(bt_status_t status, uint16_t num_packets); + +/** Callback invoked when energy details are obtained */ +/* Ctrl_state-Current controller state-Active-1,scan-2,or idle-3 state as defined by HCI spec. + * If the ctrl_state value is 0, it means the API call failed + * Time values-In milliseconds as returned by the controller + * Energy used-Value as returned by the controller + * Status-Provides the status of the read_energy_info API call */ +typedef void (*energy_info_callback)(bt_activity_energy_info *energy_info); + +/** TODO: Add callbacks for Link Up/Down and other generic + * notifications/callbacks */ + +/** Bluetooth DM callback structure. */ +typedef struct { + /** set to sizeof(bt_callbacks_t) */ + size_t size; + adapter_state_changed_callback adapter_state_changed_cb; + adapter_properties_callback adapter_properties_cb; + remote_device_properties_callback remote_device_properties_cb; + device_found_callback device_found_cb; + discovery_state_changed_callback discovery_state_changed_cb; + pin_request_callback pin_request_cb; + ssp_request_callback ssp_request_cb; + bond_state_changed_callback bond_state_changed_cb; + acl_state_changed_callback acl_state_changed_cb; + callback_thread_event thread_evt_cb; + dut_mode_recv_callback dut_mode_recv_cb; + le_test_mode_callback le_test_mode_cb; + energy_info_callback energy_info_cb; +} bt_callbacks_t; + +typedef void (*alarm_cb)(void *data); +typedef bool (*set_wake_alarm_callout)(uint64_t delay_millis, bool should_wake, alarm_cb cb, void *data); +typedef int (*acquire_wake_lock_callout)(const char *lock_name); +typedef int (*release_wake_lock_callout)(const char *lock_name); + +/** The set of functions required by bluedroid to set wake alarms and + * grab wake locks. This struct is passed into the stack through the + * |set_os_callouts| function on |bt_interface_t|. + */ +typedef struct { + /* set to sizeof(bt_os_callouts_t) */ + size_t size; + + set_wake_alarm_callout set_wake_alarm; + acquire_wake_lock_callout acquire_wake_lock; + release_wake_lock_callout release_wake_lock; +} bt_os_callouts_t; + +/** NOTE: By default, no profiles are initialized at the time of init/enable. + * Whenever the application invokes the 'init' API of a profile, then one of + * the following shall occur: + * + * 1.) If Bluetooth is not enabled, then the Bluetooth core shall mark the + * profile as enabled. Subsequently, when the application invokes the + * Bluetooth 'enable', as part of the enable sequence the profile that were + * marked shall be enabled by calling appropriate stack APIs. The + * 'adapter_properties_cb' shall return the list of UUIDs of the + * enabled profiles. + * + * 2.) If Bluetooth is enabled, then the Bluetooth core shall invoke the stack + * profile API to initialize the profile and trigger a + * 'adapter_properties_cb' with the current list of UUIDs including the + * newly added profile's UUID. + * + * The reverse shall occur whenever the profile 'cleanup' APIs are invoked + */ + +/** Represents the standard Bluetooth DM interface. */ +typedef struct { + /** set to sizeof(bt_interface_t) */ + size_t size; + /** + * Opens the interface and provides the callback routines + * to the implemenation of this interface. + */ + int (*init)(bt_callbacks_t* callbacks ); + + /** Enable Bluetooth. */ + int (*enable)(void); + + /** Disable Bluetooth. */ + int (*disable)(void); + + /** Closes the interface. */ + void (*cleanup)(void); + + /** Get all Bluetooth Adapter properties at init */ + int (*get_adapter_properties)(void); + + /** Get Bluetooth Adapter property of 'type' */ + int (*get_adapter_property)(bt_property_type_t type); + + /** Set Bluetooth Adapter property of 'type' */ + /* Based on the type, val shall be one of + * bt_bdaddr_t or bt_bdname_t or bt_scanmode_t etc + */ + int (*set_adapter_property)(const bt_property_t *property); + + /** Get all Remote Device properties */ + int (*get_remote_device_properties)(bt_bdaddr_t *remote_addr); + + /** Get Remote Device property of 'type' */ + int (*get_remote_device_property)(bt_bdaddr_t *remote_addr, + bt_property_type_t type); + + /** Set Remote Device property of 'type' */ + int (*set_remote_device_property)(bt_bdaddr_t *remote_addr, + const bt_property_t *property); + + /** Get Remote Device's service record for the given UUID */ + int (*get_remote_service_record)(bt_bdaddr_t *remote_addr, + bt_uuid_t *uuid); + + /** Start SDP to get remote services */ + int (*get_remote_services)(bt_bdaddr_t *remote_addr); + + /** Start Discovery */ + int (*start_discovery)(void); + + /** Cancel Discovery */ + int (*cancel_discovery)(void); + + /** Create Bluetooth Bonding */ + int (*create_bond)(const bt_bdaddr_t *bd_addr, int transport); + + /** Remove Bond */ + int (*remove_bond)(const bt_bdaddr_t *bd_addr); + + /** Cancel Bond */ + int (*cancel_bond)(const bt_bdaddr_t *bd_addr); + + /** + * Get the connection status for a given remote device. + * return value of 0 means the device is not connected, + * non-zero return status indicates an active connection. + */ + int (*get_connection_state)(const bt_bdaddr_t *bd_addr); + + /** BT Legacy PinKey Reply */ + /** If accept==FALSE, then pin_len and pin_code shall be 0x0 */ + int (*pin_reply)(const bt_bdaddr_t *bd_addr, uint8_t accept, + uint8_t pin_len, bt_pin_code_t *pin_code); + + /** BT SSP Reply - Just Works, Numeric Comparison and Passkey + * passkey shall be zero for BT_SSP_VARIANT_PASSKEY_COMPARISON & + * BT_SSP_VARIANT_CONSENT + * For BT_SSP_VARIANT_PASSKEY_ENTRY, if accept==FALSE, then passkey + * shall be zero */ + int (*ssp_reply)(const bt_bdaddr_t *bd_addr, bt_ssp_variant_t variant, + uint8_t accept, uint32_t passkey); + + /** Get Bluetooth profile interface */ + const void* (*get_profile_interface) (const char *profile_id); + + /** Bluetooth Test Mode APIs - Bluetooth must be enabled for these APIs */ + /* Configure DUT Mode - Use this mode to enter/exit DUT mode */ + int (*dut_mode_configure)(uint8_t enable); + + /* Send any test HCI (vendor-specific) command to the controller. Must be in DUT Mode */ + int (*dut_mode_send)(uint16_t opcode, uint8_t *buf, uint8_t len); + /** BLE Test Mode APIs */ + /* opcode MUST be one of: LE_Receiver_Test, LE_Transmitter_Test, LE_Test_End */ + int (*le_test_mode)(uint16_t opcode, uint8_t *buf, uint8_t len); + + /* enable or disable bluetooth HCI snoop log */ + int (*config_hci_snoop_log)(uint8_t enable); + + /** Sets the OS call-out functions that bluedroid needs for alarms and wake locks. + * This should be called immediately after a successful |init|. + */ + int (*set_os_callouts)(bt_os_callouts_t *callouts); + + /** Read Energy info details - return value indicates BT_STATUS_SUCCESS or BT_STATUS_NOT_READY + * Success indicates that the VSC command was sent to controller + */ + int (*read_energy_info)(); +} bt_interface_t; + +/** TODO: Need to add APIs for Service Discovery, Service authorization and + * connection management. Also need to add APIs for configuring + * properties of remote bonded devices such as name, UUID etc. */ + +typedef struct { + struct hw_device_t common; + const bt_interface_t* (*get_bluetooth_interface)(); +} bluetooth_device_t; + +typedef bluetooth_device_t bluetooth_module_t; +__END_DECLS + +#endif /* ANDROID_INCLUDE_BLUETOOTH_H */
diff --git a/android/hardware/bt_av.h b/android/hardware/bt_av.h new file mode 100644 index 0000000..5252a17 --- /dev/null +++ b/android/hardware/bt_av.h
@@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_AV_H +#define ANDROID_INCLUDE_BT_AV_H + +__BEGIN_DECLS + +/* Bluetooth AV connection states */ +typedef enum { + BTAV_CONNECTION_STATE_DISCONNECTED = 0, + BTAV_CONNECTION_STATE_CONNECTING, + BTAV_CONNECTION_STATE_CONNECTED, + BTAV_CONNECTION_STATE_DISCONNECTING +} btav_connection_state_t; + +/* Bluetooth AV datapath states */ +typedef enum { + BTAV_AUDIO_STATE_REMOTE_SUSPEND = 0, + BTAV_AUDIO_STATE_STOPPED, + BTAV_AUDIO_STATE_STARTED, +} btav_audio_state_t; + + +/** Callback for connection state change. + * state will have one of the values from btav_connection_state_t + */ +typedef void (* btav_connection_state_callback)(btav_connection_state_t state, + bt_bdaddr_t *bd_addr); + +/** Callback for audiopath state change. + * state will have one of the values from btav_audio_state_t + */ +typedef void (* btav_audio_state_callback)(btav_audio_state_t state, + bt_bdaddr_t *bd_addr); + +/** Callback for audio configuration change. + * Used only for the A2DP sink interface. + * state will have one of the values from btav_audio_state_t + * sample_rate: sample rate in Hz + * channel_count: number of channels (1 for mono, 2 for stereo) + */ +typedef void (* btav_audio_config_callback)(bt_bdaddr_t *bd_addr, + uint32_t sample_rate, + uint8_t channel_count); + +/** BT-AV callback structure. */ +typedef struct { + /** set to sizeof(btav_callbacks_t) */ + size_t size; + btav_connection_state_callback connection_state_cb; + btav_audio_state_callback audio_state_cb; + btav_audio_config_callback audio_config_cb; +} btav_callbacks_t; + +/** + * NOTE: + * + * 1. AVRCP 1.0 shall be supported initially. AVRCP passthrough commands + * shall be handled internally via uinput + * + * 2. A2DP data path shall be handled via a socket pipe between the AudioFlinger + * android_audio_hw library and the Bluetooth stack. + * + */ +/** Represents the standard BT-AV interface. + * Used for both the A2DP source and sink interfaces. + */ +typedef struct { + + /** set to sizeof(btav_interface_t) */ + size_t size; + /** + * Register the BtAv callbacks + */ + bt_status_t (*init)( btav_callbacks_t* callbacks ); + + /** connect to headset */ + bt_status_t (*connect)( bt_bdaddr_t *bd_addr ); + + /** dis-connect from headset */ + bt_status_t (*disconnect)( bt_bdaddr_t *bd_addr ); + + /** Closes the interface. */ + void (*cleanup)( void ); +} btav_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_AV_H */
diff --git a/android/hardware/bt_gatt.h b/android/hardware/bt_gatt.h new file mode 100644 index 0000000..42e14c2 --- /dev/null +++ b/android/hardware/bt_gatt.h
@@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 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. + */ + + +#ifndef ANDROID_INCLUDE_BT_GATT_H +#define ANDROID_INCLUDE_BT_GATT_H + +#include <stdint.h> +#include "bt_gatt_client.h" +#include "bt_gatt_server.h" + +__BEGIN_DECLS + +/** BT-GATT callbacks */ +typedef struct { + /** Set to sizeof(btgatt_callbacks_t) */ + size_t size; + + /** GATT Client callbacks */ + const btgatt_client_callbacks_t* client; + + /** GATT Server callbacks */ + const btgatt_server_callbacks_t* server; +} btgatt_callbacks_t; + +/** Represents the standard Bluetooth GATT interface. */ +typedef struct { + /** Set to sizeof(btgatt_interface_t) */ + size_t size; + + /** + * Initializes the interface and provides callback routines + */ + bt_status_t (*init)( const btgatt_callbacks_t* callbacks ); + + /** Closes the interface */ + void (*cleanup)( void ); + + /** Pointer to the GATT client interface methods.*/ + const btgatt_client_interface_t* client; + + /** Pointer to the GATT server interface methods.*/ + const btgatt_server_interface_t* server; +} btgatt_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_GATT_H */
diff --git a/android/hardware/bt_gatt_client.h b/android/hardware/bt_gatt_client.h new file mode 100644 index 0000000..8073dd1 --- /dev/null +++ b/android/hardware/bt_gatt_client.h
@@ -0,0 +1,417 @@ +/* + * Copyright (C) 2013 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. + */ + + +#ifndef ANDROID_INCLUDE_BT_GATT_CLIENT_H +#define ANDROID_INCLUDE_BT_GATT_CLIENT_H + +#include <stdint.h> +#include "bt_gatt_types.h" + +__BEGIN_DECLS + +/** + * Buffer sizes for maximum attribute length and maximum read/write + * operation buffer size. + */ +#define BTGATT_MAX_ATTR_LEN 600 + +/** Buffer type for unformatted reads/writes */ +typedef struct +{ + uint8_t value[BTGATT_MAX_ATTR_LEN]; + uint16_t len; +} btgatt_unformatted_value_t; + +/** Parameters for GATT read operations */ +typedef struct +{ + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + btgatt_gatt_id_t descr_id; + btgatt_unformatted_value_t value; + uint16_t value_type; + uint8_t status; +} btgatt_read_params_t; + +/** Parameters for GATT write operations */ +typedef struct +{ + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + btgatt_gatt_id_t descr_id; + uint8_t status; +} btgatt_write_params_t; + +/** Attribute change notification parameters */ +typedef struct +{ + uint8_t value[BTGATT_MAX_ATTR_LEN]; + bt_bdaddr_t bda; + btgatt_srvc_id_t srvc_id; + btgatt_gatt_id_t char_id; + uint16_t len; + uint8_t is_notify; +} btgatt_notify_params_t; + +typedef struct +{ + bt_bdaddr_t *bda1; + bt_uuid_t *uuid1; + uint16_t u1; + uint16_t u2; + uint16_t u3; + uint16_t u4; + uint16_t u5; +} btgatt_test_params_t; + +/** BT-GATT Client callback structure. */ + +/** Callback invoked in response to register_client */ +typedef void (*register_client_callback)(int status, int client_if, + bt_uuid_t *app_uuid); + +/** Callback for scan results */ +typedef void (*scan_result_callback)(bt_bdaddr_t* bda, int rssi, uint8_t* adv_data); + +/** GATT open callback invoked in response to open */ +typedef void (*connect_callback)(int conn_id, int status, int client_if, bt_bdaddr_t* bda); + +/** Callback invoked in response to close */ +typedef void (*disconnect_callback)(int conn_id, int status, + int client_if, bt_bdaddr_t* bda); + +/** + * Invoked in response to search_service when the GATT service search + * has been completed. + */ +typedef void (*search_complete_callback)(int conn_id, int status); + +/** Reports GATT services on a remote device */ +typedef void (*search_result_callback)( int conn_id, btgatt_srvc_id_t *srvc_id); + +/** GATT characteristic enumeration result callback */ +typedef void (*get_characteristic_callback)(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + int char_prop); + +/** GATT descriptor enumeration result callback */ +typedef void (*get_descriptor_callback)(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *descr_id); + +/** GATT included service enumeration result callback */ +typedef void (*get_included_service_callback)(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, btgatt_srvc_id_t *incl_srvc_id); + +/** Callback invoked in response to [de]register_for_notification */ +typedef void (*register_for_notification_callback)(int conn_id, + int registered, int status, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id); + +/** + * Remote device notification callback, invoked when a remote device sends + * a notification or indication that a client has registered for. + */ +typedef void (*notify_callback)(int conn_id, btgatt_notify_params_t *p_data); + +/** Reports result of a GATT read operation */ +typedef void (*read_characteristic_callback)(int conn_id, int status, + btgatt_read_params_t *p_data); + +/** GATT write characteristic operation callback */ +typedef void (*write_characteristic_callback)(int conn_id, int status, + btgatt_write_params_t *p_data); + +/** GATT execute prepared write callback */ +typedef void (*execute_write_callback)(int conn_id, int status); + +/** Callback invoked in response to read_descriptor */ +typedef void (*read_descriptor_callback)(int conn_id, int status, + btgatt_read_params_t *p_data); + +/** Callback invoked in response to write_descriptor */ +typedef void (*write_descriptor_callback)(int conn_id, int status, + btgatt_write_params_t *p_data); + +/** Callback triggered in response to read_remote_rssi */ +typedef void (*read_remote_rssi_callback)(int client_if, bt_bdaddr_t* bda, + int rssi, int status); + +/** + * Callback indicating the status of a listen() operation + */ +typedef void (*listen_callback)(int status, int server_if); + +/** Callback invoked when the MTU for a given connection changes */ +typedef void (*configure_mtu_callback)(int conn_id, int status, int mtu); + +/** Callback invoked when a scan filter configuration command has completed */ +typedef void (*scan_filter_cfg_callback)(int action, int client_if, int status, int filt_type, + int avbl_space); + +/** Callback invoked when scan param has been added, cleared, or deleted */ +typedef void (*scan_filter_param_callback)(int action, int client_if, int status, + int avbl_space); + +/** Callback invoked when a scan filter configuration command has completed */ +typedef void (*scan_filter_status_callback)(int enable, int client_if, int status); + +/** Callback invoked when multi-adv enable operation has completed */ +typedef void (*multi_adv_enable_callback)(int client_if, int status); + +/** Callback invoked when multi-adv param update operation has completed */ +typedef void (*multi_adv_update_callback)(int client_if, int status); + +/** Callback invoked when multi-adv instance data set operation has completed */ +typedef void (*multi_adv_data_callback)(int client_if, int status); + +/** Callback invoked when multi-adv disable operation has completed */ +typedef void (*multi_adv_disable_callback)(int client_if, int status); + +/** + * Callback notifying an application that a remote device connection is currently congested + * and cannot receive any more data. An application should avoid sending more data until + * a further callback is received indicating the congestion status has been cleared. + */ +typedef void (*congestion_callback)(int conn_id, bool congested); +/** Callback invoked when batchscan storage config operation has completed */ +typedef void (*batchscan_cfg_storage_callback)(int client_if, int status); + +/** Callback invoked when batchscan enable / disable operation has completed */ +typedef void (*batchscan_enable_disable_callback)(int action, int client_if, int status); + +/** Callback invoked when batchscan reports are obtained */ +typedef void (*batchscan_reports_callback)(int client_if, int status, int report_format, + int num_records, int data_len, uint8_t* rep_data); + +/** Callback invoked when batchscan storage threshold limit is crossed */ +typedef void (*batchscan_threshold_callback)(int client_if); + +/** Track ADV VSE callback invoked when tracked device is found or lost */ +typedef void (*track_adv_event_callback)(int client_if, int filt_index, int addr_type, + bt_bdaddr_t* bda, int adv_state); + +typedef struct { + register_client_callback register_client_cb; + scan_result_callback scan_result_cb; + connect_callback open_cb; + disconnect_callback close_cb; + search_complete_callback search_complete_cb; + search_result_callback search_result_cb; + get_characteristic_callback get_characteristic_cb; + get_descriptor_callback get_descriptor_cb; + get_included_service_callback get_included_service_cb; + register_for_notification_callback register_for_notification_cb; + notify_callback notify_cb; + read_characteristic_callback read_characteristic_cb; + write_characteristic_callback write_characteristic_cb; + read_descriptor_callback read_descriptor_cb; + write_descriptor_callback write_descriptor_cb; + execute_write_callback execute_write_cb; + read_remote_rssi_callback read_remote_rssi_cb; + listen_callback listen_cb; + configure_mtu_callback configure_mtu_cb; + scan_filter_cfg_callback scan_filter_cfg_cb; + scan_filter_param_callback scan_filter_param_cb; + scan_filter_status_callback scan_filter_status_cb; + multi_adv_enable_callback multi_adv_enable_cb; + multi_adv_update_callback multi_adv_update_cb; + multi_adv_data_callback multi_adv_data_cb; + multi_adv_disable_callback multi_adv_disable_cb; + congestion_callback congestion_cb; + batchscan_cfg_storage_callback batchscan_cfg_storage_cb; + batchscan_enable_disable_callback batchscan_enb_disable_cb; + batchscan_reports_callback batchscan_reports_cb; + batchscan_threshold_callback batchscan_threshold_cb; + track_adv_event_callback track_adv_event_cb; +} btgatt_client_callbacks_t; + +/** Represents the standard BT-GATT client interface. */ + +typedef struct { + /** Registers a GATT client application with the stack */ + bt_status_t (*register_client)( bt_uuid_t *uuid ); + + /** Unregister a client application from the stack */ + bt_status_t (*unregister_client)(int client_if ); + + /** Start or stop LE device scanning */ + bt_status_t (*scan)( bool start ); + + /** Create a connection to a remote LE or dual-mode device */ + bt_status_t (*connect)( int client_if, const bt_bdaddr_t *bd_addr, + bool is_direct, int transport ); + + /** Disconnect a remote device or cancel a pending connection */ + bt_status_t (*disconnect)( int client_if, const bt_bdaddr_t *bd_addr, + int conn_id); + + /** Start or stop advertisements to listen for incoming connections */ + bt_status_t (*listen)(int client_if, bool start); + + /** Clear the attribute cache for a given device */ + bt_status_t (*refresh)( int client_if, const bt_bdaddr_t *bd_addr ); + + /** + * Enumerate all GATT services on a connected device. + * Optionally, the results can be filtered for a given UUID. + */ + bt_status_t (*search_service)(int conn_id, bt_uuid_t *filter_uuid ); + + /** + * Enumerate included services for a given service. + * Set start_incl_srvc_id to NULL to get the first included service. + */ + bt_status_t (*get_included_service)( int conn_id, btgatt_srvc_id_t *srvc_id, + btgatt_srvc_id_t *start_incl_srvc_id); + + /** + * Enumerate characteristics for a given service. + * Set start_char_id to NULL to get the first characteristic. + */ + bt_status_t (*get_characteristic)( int conn_id, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *start_char_id); + + /** + * Enumerate descriptors for a given characteristic. + * Set start_descr_id to NULL to get the first descriptor. + */ + bt_status_t (*get_descriptor)( int conn_id, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *start_descr_id); + + /** Read a characteristic on a remote device */ + bt_status_t (*read_characteristic)( int conn_id, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + int auth_req ); + + /** Write a remote characteristic */ + bt_status_t (*write_characteristic)(int conn_id, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + int write_type, int len, int auth_req, + char* p_value); + + /** Read the descriptor for a given characteristic */ + bt_status_t (*read_descriptor)(int conn_id, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *descr_id, int auth_req); + + /** Write a remote descriptor for a given characteristic */ + bt_status_t (*write_descriptor)( int conn_id, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *descr_id, int write_type, int len, + int auth_req, char* p_value); + + /** Execute a prepared write operation */ + bt_status_t (*execute_write)(int conn_id, int execute); + + /** + * Register to receive notifications or indications for a given + * characteristic + */ + bt_status_t (*register_for_notification)( int client_if, + const bt_bdaddr_t *bd_addr, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id); + + /** Deregister a previous request for notifications/indications */ + bt_status_t (*deregister_for_notification)( int client_if, + const bt_bdaddr_t *bd_addr, btgatt_srvc_id_t *srvc_id, + btgatt_gatt_id_t *char_id); + + /** Request RSSI for a given remote device */ + bt_status_t (*read_remote_rssi)( int client_if, const bt_bdaddr_t *bd_addr); + + /** Setup scan filter params */ + bt_status_t (*scan_filter_param_setup)(int client_if, int action, int filt_index, int feat_seln, + int list_logic_type, int filt_logic_type, int rssi_high_thres, + int rssi_low_thres, int dely_mode, int found_timeout, + int lost_timeout, int found_timeout_cnt); + + + /** Configure a scan filter condition */ + bt_status_t (*scan_filter_add_remove)(int client_if, int action, int filt_type, + int filt_index, int company_id, + int company_id_mask, const bt_uuid_t *p_uuid, + const bt_uuid_t *p_uuid_mask, const bt_bdaddr_t *bd_addr, + char addr_type, int data_len, char* p_data, int mask_len, + char* p_mask); + + /** Clear all scan filter conditions for specific filter index*/ + bt_status_t (*scan_filter_clear)(int client_if, int filt_index); + + /** Enable / disable scan filter feature*/ + bt_status_t (*scan_filter_enable)(int client_if, bool enable); + + /** Determine the type of the remote device (LE, BR/EDR, Dual-mode) */ + int (*get_device_type)( const bt_bdaddr_t *bd_addr ); + + /** Set the advertising data or scan response data */ + bt_status_t (*set_adv_data)(int client_if, bool set_scan_rsp, bool include_name, + bool include_txpower, int min_interval, int max_interval, int appearance, + uint16_t manufacturer_len, char* manufacturer_data, + uint16_t service_data_len, char* service_data, + uint16_t service_uuid_len, char* service_uuid); + + /** Configure the MTU for a given connection */ + bt_status_t (*configure_mtu)(int conn_id, int mtu); + + /** Request a connection parameter update */ + bt_status_t (*conn_parameter_update)(const bt_bdaddr_t *bd_addr, int min_interval, + int max_interval, int latency, int timeout); + + /** Sets the LE scan interval and window in units of N*0.625 msec */ + bt_status_t (*set_scan_parameters)(int scan_interval, int scan_window); + + /* Setup the parameters as per spec, user manual specified values and enable multi ADV */ + bt_status_t (*multi_adv_enable)(int client_if, int min_interval,int max_interval,int adv_type, + int chnl_map, int tx_power, int timeout_s); + + /* Update the parameters as per spec, user manual specified values and restart multi ADV */ + bt_status_t (*multi_adv_update)(int client_if, int min_interval,int max_interval,int adv_type, + int chnl_map, int tx_power, int timeout_s); + + /* Setup the data for the specified instance */ + bt_status_t (*multi_adv_set_inst_data)(int client_if, bool set_scan_rsp, bool include_name, + bool incl_txpower, int appearance, int manufacturer_len, + char* manufacturer_data, int service_data_len, + char* service_data, int service_uuid_len, char* service_uuid); + + /* Disable the multi adv instance */ + bt_status_t (*multi_adv_disable)(int client_if); + + /* Configure the batchscan storage */ + bt_status_t (*batchscan_cfg_storage)(int client_if, int batch_scan_full_max, + int batch_scan_trunc_max, int batch_scan_notify_threshold); + + /* Enable batchscan */ + bt_status_t (*batchscan_enb_batch_scan)(int client_if, int scan_mode, + int scan_interval, int scan_window, int addr_type, int discard_rule); + + /* Disable batchscan */ + bt_status_t (*batchscan_dis_batch_scan)(int client_if); + + /* Read out batchscan reports */ + bt_status_t (*batchscan_read_reports)(int client_if, int scan_mode); + + /** Test mode interface */ + bt_status_t (*test_command)( int command, btgatt_test_params_t* params); + +} btgatt_client_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_GATT_CLIENT_H */
diff --git a/android/hardware/bt_gatt_server.h b/android/hardware/bt_gatt_server.h new file mode 100644 index 0000000..0d6cc1e --- /dev/null +++ b/android/hardware/bt_gatt_server.h
@@ -0,0 +1,196 @@ +/* + * Copyright (C) 2013 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. + */ + + +#ifndef ANDROID_INCLUDE_BT_GATT_SERVER_H +#define ANDROID_INCLUDE_BT_GATT_SERVER_H + +#include <stdint.h> + +#include "bt_gatt_types.h" + +__BEGIN_DECLS + +/** GATT value type used in response to remote read requests */ +typedef struct +{ + uint8_t value[BTGATT_MAX_ATTR_LEN]; + uint16_t handle; + uint16_t offset; + uint16_t len; + uint8_t auth_req; +} btgatt_value_t; + +/** GATT remote read request response type */ +typedef union +{ + btgatt_value_t attr_value; + uint16_t handle; +} btgatt_response_t; + +/** BT-GATT Server callback structure. */ + +/** Callback invoked in response to register_server */ +typedef void (*register_server_callback)(int status, int server_if, + bt_uuid_t *app_uuid); + +/** Callback indicating that a remote device has connected or been disconnected */ +typedef void (*connection_callback)(int conn_id, int server_if, int connected, + bt_bdaddr_t *bda); + +/** Callback invoked in response to create_service */ +typedef void (*service_added_callback)(int status, int server_if, + btgatt_srvc_id_t *srvc_id, int srvc_handle); + +/** Callback indicating that an included service has been added to a service */ +typedef void (*included_service_added_callback)(int status, int server_if, + int srvc_handle, int incl_srvc_handle); + +/** Callback invoked when a characteristic has been added to a service */ +typedef void (*characteristic_added_callback)(int status, int server_if, + bt_uuid_t *uuid, int srvc_handle, int char_handle); + +/** Callback invoked when a descriptor has been added to a characteristic */ +typedef void (*descriptor_added_callback)(int status, int server_if, + bt_uuid_t *uuid, int srvc_handle, int descr_handle); + +/** Callback invoked in response to start_service */ +typedef void (*service_started_callback)(int status, int server_if, + int srvc_handle); + +/** Callback invoked in response to stop_service */ +typedef void (*service_stopped_callback)(int status, int server_if, + int srvc_handle); + +/** Callback triggered when a service has been deleted */ +typedef void (*service_deleted_callback)(int status, int server_if, + int srvc_handle); + +/** + * Callback invoked when a remote device has requested to read a characteristic + * or descriptor. The application must respond by calling send_response + */ +typedef void (*request_read_callback)(int conn_id, int trans_id, bt_bdaddr_t *bda, + int attr_handle, int offset, bool is_long); + +/** + * Callback invoked when a remote device has requested to write to a + * characteristic or descriptor. + */ +typedef void (*request_write_callback)(int conn_id, int trans_id, bt_bdaddr_t *bda, + int attr_handle, int offset, int length, + bool need_rsp, bool is_prep, uint8_t* value); + +/** Callback invoked when a previously prepared write is to be executed */ +typedef void (*request_exec_write_callback)(int conn_id, int trans_id, + bt_bdaddr_t *bda, int exec_write); + +/** + * Callback triggered in response to send_response if the remote device + * sends a confirmation. + */ +typedef void (*response_confirmation_callback)(int status, int handle); + +/** + * Callback confirming that a notification or indication has been sent + * to a remote device. + */ +typedef void (*indication_sent_callback)(int conn_id, int status); + +/** + * Callback notifying an application that a remote device connection is currently congested + * and cannot receive any more data. An application should avoid sending more data until + * a further callback is received indicating the congestion status has been cleared. + */ +typedef void (*congestion_callback)(int conn_id, bool congested); + +/** Callback invoked when the MTU for a given connection changes */ +typedef void (*mtu_changed_callback)(int conn_id, int mtu); + +typedef struct { + register_server_callback register_server_cb; + connection_callback connection_cb; + service_added_callback service_added_cb; + included_service_added_callback included_service_added_cb; + characteristic_added_callback characteristic_added_cb; + descriptor_added_callback descriptor_added_cb; + service_started_callback service_started_cb; + service_stopped_callback service_stopped_cb; + service_deleted_callback service_deleted_cb; + request_read_callback request_read_cb; + request_write_callback request_write_cb; + request_exec_write_callback request_exec_write_cb; + response_confirmation_callback response_confirmation_cb; + indication_sent_callback indication_sent_cb; + congestion_callback congestion_cb; + mtu_changed_callback mtu_changed_cb; +} btgatt_server_callbacks_t; + +/** Represents the standard BT-GATT server interface. */ +typedef struct { + /** Registers a GATT server application with the stack */ + bt_status_t (*register_server)( bt_uuid_t *uuid ); + + /** Unregister a server application from the stack */ + bt_status_t (*unregister_server)(int server_if ); + + /** Create a connection to a remote peripheral */ + bt_status_t (*connect)(int server_if, const bt_bdaddr_t *bd_addr, + bool is_direct, int transport); + + /** Disconnect an established connection or cancel a pending one */ + bt_status_t (*disconnect)(int server_if, const bt_bdaddr_t *bd_addr, + int conn_id ); + + /** Create a new service */ + bt_status_t (*add_service)( int server_if, btgatt_srvc_id_t *srvc_id, int num_handles); + + /** Assign an included service to it's parent service */ + bt_status_t (*add_included_service)( int server_if, int service_handle, int included_handle); + + /** Add a characteristic to a service */ + bt_status_t (*add_characteristic)( int server_if, + int service_handle, bt_uuid_t *uuid, + int properties, int permissions); + + /** Add a descriptor to a given service */ + bt_status_t (*add_descriptor)(int server_if, int service_handle, + bt_uuid_t *uuid, int permissions); + + /** Starts a local service */ + bt_status_t (*start_service)(int server_if, int service_handle, + int transport); + + /** Stops a local service */ + bt_status_t (*stop_service)(int server_if, int service_handle); + + /** Delete a local service */ + bt_status_t (*delete_service)(int server_if, int service_handle); + + /** Send value indication to a remote device */ + bt_status_t (*send_indication)(int server_if, int attribute_handle, + int conn_id, int len, int confirm, + char* p_value); + + /** Send a response to a read/write operation */ + bt_status_t (*send_response)(int conn_id, int trans_id, + int status, btgatt_response_t *response); + +} btgatt_server_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_GATT_CLIENT_H */
diff --git a/android/hardware/bt_gatt_types.h b/android/hardware/bt_gatt_types.h new file mode 100644 index 0000000..e037ddc --- /dev/null +++ b/android/hardware/bt_gatt_types.h
@@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 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. + */ + + +#ifndef ANDROID_INCLUDE_BT_GATT_TYPES_H +#define ANDROID_INCLUDE_BT_GATT_TYPES_H + +#include <stdint.h> +#include <stdbool.h> + +__BEGIN_DECLS + +/** + * GATT Service types + */ +#define BTGATT_SERVICE_TYPE_PRIMARY 0 +#define BTGATT_SERVICE_TYPE_SECONDARY 1 + +/** GATT ID adding instance id tracking to the UUID */ +typedef struct +{ + bt_uuid_t uuid; + uint8_t inst_id; +} btgatt_gatt_id_t; + +/** GATT Service ID also identifies the service type (primary/secondary) */ +typedef struct +{ + btgatt_gatt_id_t id; + uint8_t is_primary; +} btgatt_srvc_id_t; + +/** Preferred physical Transport for GATT connection */ +typedef enum +{ + GATT_TRANSPORT_AUTO, + GATT_TRANSPORT_BREDR, + GATT_TRANSPORT_LE +} btgatt_transport_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_GATT_TYPES_H */
diff --git a/android/hardware/bt_hf.h b/android/hardware/bt_hf.h new file mode 100644 index 0000000..7dcb40a --- /dev/null +++ b/android/hardware/bt_hf.h
@@ -0,0 +1,301 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_HF_H +#define ANDROID_INCLUDE_BT_HF_H + +__BEGIN_DECLS + +/* AT response code - OK/Error */ +typedef enum { + BTHF_AT_RESPONSE_ERROR = 0, + BTHF_AT_RESPONSE_OK +} bthf_at_response_t; + +typedef enum { + BTHF_CONNECTION_STATE_DISCONNECTED = 0, + BTHF_CONNECTION_STATE_CONNECTING, + BTHF_CONNECTION_STATE_CONNECTED, + BTHF_CONNECTION_STATE_SLC_CONNECTED, + BTHF_CONNECTION_STATE_DISCONNECTING +} bthf_connection_state_t; + +typedef enum { + BTHF_AUDIO_STATE_DISCONNECTED = 0, + BTHF_AUDIO_STATE_CONNECTING, + BTHF_AUDIO_STATE_CONNECTED, + BTHF_AUDIO_STATE_DISCONNECTING +} bthf_audio_state_t; + +typedef enum { + BTHF_VR_STATE_STOPPED = 0, + BTHF_VR_STATE_STARTED +} bthf_vr_state_t; + +typedef enum { + BTHF_VOLUME_TYPE_SPK = 0, + BTHF_VOLUME_TYPE_MIC +} bthf_volume_type_t; + +/* Noise Reduction and Echo Cancellation */ +typedef enum +{ + BTHF_NREC_STOP, + BTHF_NREC_START +} bthf_nrec_t; + +/* WBS codec setting */ +typedef enum +{ + BTHF_WBS_NONE, + BTHF_WBS_NO, + BTHF_WBS_YES +}bthf_wbs_config_t; + +/* CHLD - Call held handling */ +typedef enum +{ + BTHF_CHLD_TYPE_RELEASEHELD, // Terminate all held or set UDUB("busy") to a waiting call + BTHF_CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD, // Terminate all active calls and accepts a waiting/held call + BTHF_CHLD_TYPE_HOLDACTIVE_ACCEPTHELD, // Hold all active calls and accepts a waiting/held call + BTHF_CHLD_TYPE_ADDHELDTOCONF, // Add all held calls to a conference +} bthf_chld_type_t; + +/** Callback for connection state change. + * state will have one of the values from BtHfConnectionState + */ +typedef void (* bthf_connection_state_callback)(bthf_connection_state_t state, bt_bdaddr_t *bd_addr); + +/** Callback for audio connection state change. + * state will have one of the values from BtHfAudioState + */ +typedef void (* bthf_audio_state_callback)(bthf_audio_state_t state, bt_bdaddr_t *bd_addr); + +/** Callback for VR connection state change. + * state will have one of the values from BtHfVRState + */ +typedef void (* bthf_vr_cmd_callback)(bthf_vr_state_t state, bt_bdaddr_t *bd_addr); + +/** Callback for answer incoming call (ATA) + */ +typedef void (* bthf_answer_call_cmd_callback)(bt_bdaddr_t *bd_addr); + +/** Callback for disconnect call (AT+CHUP) + */ +typedef void (* bthf_hangup_call_cmd_callback)(bt_bdaddr_t *bd_addr); + +/** Callback for disconnect call (AT+CHUP) + * type will denote Speaker/Mic gain (BtHfVolumeControl). + */ +typedef void (* bthf_volume_cmd_callback)(bthf_volume_type_t type, int volume, bt_bdaddr_t *bd_addr); + +/** Callback for dialing an outgoing call + * If number is NULL, redial + */ +typedef void (* bthf_dial_call_cmd_callback)(char *number, bt_bdaddr_t *bd_addr); + +/** Callback for sending DTMF tones + * tone contains the dtmf character to be sent + */ +typedef void (* bthf_dtmf_cmd_callback)(char tone, bt_bdaddr_t *bd_addr); + +/** Callback for enabling/disabling noise reduction/echo cancellation + * value will be 1 to enable, 0 to disable + */ +typedef void (* bthf_nrec_cmd_callback)(bthf_nrec_t nrec, bt_bdaddr_t *bd_addr); + +/** Callback for AT+BCS and event from BAC + * WBS enable, WBS disable + */ +typedef void (* bthf_wbs_callback)(bthf_wbs_config_t wbs, bt_bdaddr_t *bd_addr); + +/** Callback for call hold handling (AT+CHLD) + * value will contain the call hold command (0, 1, 2, 3) + */ +typedef void (* bthf_chld_cmd_callback)(bthf_chld_type_t chld, bt_bdaddr_t *bd_addr); + +/** Callback for CNUM (subscriber number) + */ +typedef void (* bthf_cnum_cmd_callback)(bt_bdaddr_t *bd_addr); + +/** Callback for indicators (CIND) + */ +typedef void (* bthf_cind_cmd_callback)(bt_bdaddr_t *bd_addr); + +/** Callback for operator selection (COPS) + */ +typedef void (* bthf_cops_cmd_callback)(bt_bdaddr_t *bd_addr); + +/** Callback for call list (AT+CLCC) + */ +typedef void (* bthf_clcc_cmd_callback) (bt_bdaddr_t *bd_addr); + +/** Callback for unknown AT command recd from HF + * at_string will contain the unparsed AT string + */ +typedef void (* bthf_unknown_at_cmd_callback)(char *at_string, bt_bdaddr_t *bd_addr); + +/** Callback for keypressed (HSP) event. + */ +typedef void (* bthf_key_pressed_cmd_callback)(bt_bdaddr_t *bd_addr); + +/** BT-HF callback structure. */ +typedef struct { + /** set to sizeof(BtHfCallbacks) */ + size_t size; + bthf_connection_state_callback connection_state_cb; + bthf_audio_state_callback audio_state_cb; + bthf_vr_cmd_callback vr_cmd_cb; + bthf_answer_call_cmd_callback answer_call_cmd_cb; + bthf_hangup_call_cmd_callback hangup_call_cmd_cb; + bthf_volume_cmd_callback volume_cmd_cb; + bthf_dial_call_cmd_callback dial_call_cmd_cb; + bthf_dtmf_cmd_callback dtmf_cmd_cb; + bthf_nrec_cmd_callback nrec_cmd_cb; + bthf_wbs_callback wbs_cb; + bthf_chld_cmd_callback chld_cmd_cb; + bthf_cnum_cmd_callback cnum_cmd_cb; + bthf_cind_cmd_callback cind_cmd_cb; + bthf_cops_cmd_callback cops_cmd_cb; + bthf_clcc_cmd_callback clcc_cmd_cb; + bthf_unknown_at_cmd_callback unknown_at_cmd_cb; + bthf_key_pressed_cmd_callback key_pressed_cmd_cb; +} bthf_callbacks_t; + +/** Network Status */ +typedef enum +{ + BTHF_NETWORK_STATE_NOT_AVAILABLE = 0, + BTHF_NETWORK_STATE_AVAILABLE +} bthf_network_state_t; + +/** Service type */ +typedef enum +{ + BTHF_SERVICE_TYPE_HOME = 0, + BTHF_SERVICE_TYPE_ROAMING +} bthf_service_type_t; + +typedef enum { + BTHF_CALL_STATE_ACTIVE = 0, + BTHF_CALL_STATE_HELD, + BTHF_CALL_STATE_DIALING, + BTHF_CALL_STATE_ALERTING, + BTHF_CALL_STATE_INCOMING, + BTHF_CALL_STATE_WAITING, + BTHF_CALL_STATE_IDLE +} bthf_call_state_t; + +typedef enum { + BTHF_CALL_DIRECTION_OUTGOING = 0, + BTHF_CALL_DIRECTION_INCOMING +} bthf_call_direction_t; + +typedef enum { + BTHF_CALL_TYPE_VOICE = 0, + BTHF_CALL_TYPE_DATA, + BTHF_CALL_TYPE_FAX +} bthf_call_mode_t; + +typedef enum { + BTHF_CALL_MPTY_TYPE_SINGLE = 0, + BTHF_CALL_MPTY_TYPE_MULTI +} bthf_call_mpty_type_t; + +typedef enum { + BTHF_CALL_ADDRTYPE_UNKNOWN = 0x81, + BTHF_CALL_ADDRTYPE_INTERNATIONAL = 0x91 +} bthf_call_addrtype_t; +/** Represents the standard BT-HF interface. */ +typedef struct { + + /** set to sizeof(BtHfInterface) */ + size_t size; + /** + * Register the BtHf callbacks + */ + bt_status_t (*init)( bthf_callbacks_t* callbacks, int max_hf_clients); + + /** connect to headset */ + bt_status_t (*connect)( bt_bdaddr_t *bd_addr ); + + /** dis-connect from headset */ + bt_status_t (*disconnect)( bt_bdaddr_t *bd_addr ); + + /** create an audio connection */ + bt_status_t (*connect_audio)( bt_bdaddr_t *bd_addr ); + + /** close the audio connection */ + bt_status_t (*disconnect_audio)( bt_bdaddr_t *bd_addr ); + + /** start voice recognition */ + bt_status_t (*start_voice_recognition)( bt_bdaddr_t *bd_addr ); + + /** stop voice recognition */ + bt_status_t (*stop_voice_recognition)( bt_bdaddr_t *bd_addr ); + + /** volume control */ + bt_status_t (*volume_control) (bthf_volume_type_t type, int volume, bt_bdaddr_t *bd_addr ); + + /** Combined device status change notification */ + bt_status_t (*device_status_notification)(bthf_network_state_t ntk_state, bthf_service_type_t svc_type, int signal, + int batt_chg); + + /** Response for COPS command */ + bt_status_t (*cops_response)(const char *cops, bt_bdaddr_t *bd_addr ); + + /** Response for CIND command */ + bt_status_t (*cind_response)(int svc, int num_active, int num_held, bthf_call_state_t call_setup_state, + int signal, int roam, int batt_chg, bt_bdaddr_t *bd_addr ); + + /** Pre-formatted AT response, typically in response to unknown AT cmd */ + bt_status_t (*formatted_at_response)(const char *rsp, bt_bdaddr_t *bd_addr ); + + /** ok/error response + * ERROR (0) + * OK (1) + */ + bt_status_t (*at_response) (bthf_at_response_t response_code, int error_code, bt_bdaddr_t *bd_addr ); + + /** response for CLCC command + * Can be iteratively called for each call index + * Call index of 0 will be treated as NULL termination (Completes response) + */ + bt_status_t (*clcc_response) (int index, bthf_call_direction_t dir, + bthf_call_state_t state, bthf_call_mode_t mode, + bthf_call_mpty_type_t mpty, const char *number, + bthf_call_addrtype_t type, bt_bdaddr_t *bd_addr ); + + /** notify of a call state change + * Each update notifies + * 1. Number of active/held/ringing calls + * 2. call_state: This denotes the state change that triggered this msg + * This will take one of the values from BtHfCallState + * 3. number & type: valid only for incoming & waiting call + */ + bt_status_t (*phone_state_change) (int num_active, int num_held, bthf_call_state_t call_setup_state, + const char *number, bthf_call_addrtype_t type); + + /** Closes the interface. */ + void (*cleanup)( void ); + + /** configureation for the SCO codec */ + bt_status_t (*configure_wbs)( bt_bdaddr_t *bd_addr ,bthf_wbs_config_t config ); +} bthf_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_HF_H */
diff --git a/android/hardware/bt_hf_client.h b/android/hardware/bt_hf_client.h new file mode 100644 index 0000000..8acf1b2 --- /dev/null +++ b/android/hardware/bt_hf_client.h
@@ -0,0 +1,363 @@ +/* + * Copyright (C) 2012-2014 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. + */ + +#ifndef ANDROID_INCLUDE_BT_HF_CLIENT_H +#define ANDROID_INCLUDE_BT_HF_CLIENT_H + +__BEGIN_DECLS + +typedef enum { + BTHF_CLIENT_CONNECTION_STATE_DISCONNECTED = 0, + BTHF_CLIENT_CONNECTION_STATE_CONNECTING, + BTHF_CLIENT_CONNECTION_STATE_CONNECTED, + BTHF_CLIENT_CONNECTION_STATE_SLC_CONNECTED, + BTHF_CLIENT_CONNECTION_STATE_DISCONNECTING +} bthf_client_connection_state_t; + +typedef enum { + BTHF_CLIENT_AUDIO_STATE_DISCONNECTED = 0, + BTHF_CLIENT_AUDIO_STATE_CONNECTING, + BTHF_CLIENT_AUDIO_STATE_CONNECTED, + BTHF_CLIENT_AUDIO_STATE_CONNECTED_MSBC, +} bthf_client_audio_state_t; + +typedef enum { + BTHF_CLIENT_VR_STATE_STOPPED = 0, + BTHF_CLIENT_VR_STATE_STARTED +} bthf_client_vr_state_t; + +typedef enum { + BTHF_CLIENT_VOLUME_TYPE_SPK = 0, + BTHF_CLIENT_VOLUME_TYPE_MIC +} bthf_client_volume_type_t; + +typedef enum +{ + BTHF_CLIENT_NETWORK_STATE_NOT_AVAILABLE = 0, + BTHF_CLIENT_NETWORK_STATE_AVAILABLE +} bthf_client_network_state_t; + +typedef enum +{ + BTHF_CLIENT_SERVICE_TYPE_HOME = 0, + BTHF_CLIENT_SERVICE_TYPE_ROAMING +} bthf_client_service_type_t; + +typedef enum { + BTHF_CLIENT_CALL_STATE_ACTIVE = 0, + BTHF_CLIENT_CALL_STATE_HELD, + BTHF_CLIENT_CALL_STATE_DIALING, + BTHF_CLIENT_CALL_STATE_ALERTING, + BTHF_CLIENT_CALL_STATE_INCOMING, + BTHF_CLIENT_CALL_STATE_WAITING, + BTHF_CLIENT_CALL_STATE_HELD_BY_RESP_HOLD, +} bthf_client_call_state_t; + +typedef enum { + BTHF_CLIENT_CALL_NO_CALLS_IN_PROGRESS = 0, + BTHF_CLIENT_CALL_CALLS_IN_PROGRESS +} bthf_client_call_t; + +typedef enum { + BTHF_CLIENT_CALLSETUP_NONE = 0, + BTHF_CLIENT_CALLSETUP_INCOMING, + BTHF_CLIENT_CALLSETUP_OUTGOING, + BTHF_CLIENT_CALLSETUP_ALERTING + +} bthf_client_callsetup_t; + +typedef enum { + BTHF_CLIENT_CALLHELD_NONE = 0, + BTHF_CLIENT_CALLHELD_HOLD_AND_ACTIVE, + BTHF_CLIENT_CALLHELD_HOLD, +} bthf_client_callheld_t; + +typedef enum { + BTHF_CLIENT_RESP_AND_HOLD_HELD = 0, + BTRH_CLIENT_RESP_AND_HOLD_ACCEPT, + BTRH_CLIENT_RESP_AND_HOLD_REJECT, +} bthf_client_resp_and_hold_t; + +typedef enum { + BTHF_CLIENT_CALL_DIRECTION_OUTGOING = 0, + BTHF_CLIENT_CALL_DIRECTION_INCOMING +} bthf_client_call_direction_t; + +typedef enum { + BTHF_CLIENT_CALL_MPTY_TYPE_SINGLE = 0, + BTHF_CLIENT_CALL_MPTY_TYPE_MULTI +} bthf_client_call_mpty_type_t; + +typedef enum { + BTHF_CLIENT_CMD_COMPLETE_OK = 0, + BTHF_CLIENT_CMD_COMPLETE_ERROR, + BTHF_CLIENT_CMD_COMPLETE_ERROR_NO_CARRIER, + BTHF_CLIENT_CMD_COMPLETE_ERROR_BUSY, + BTHF_CLIENT_CMD_COMPLETE_ERROR_NO_ANSWER, + BTHF_CLIENT_CMD_COMPLETE_ERROR_DELAYED, + BTHF_CLIENT_CMD_COMPLETE_ERROR_BLACKLISTED, + BTHF_CLIENT_CMD_COMPLETE_ERROR_CME +} bthf_client_cmd_complete_t; + +typedef enum { + BTHF_CLIENT_CALL_ACTION_CHLD_0 = 0, + BTHF_CLIENT_CALL_ACTION_CHLD_1, + BTHF_CLIENT_CALL_ACTION_CHLD_2, + BTHF_CLIENT_CALL_ACTION_CHLD_3, + BTHF_CLIENT_CALL_ACTION_CHLD_4, + BTHF_CLIENT_CALL_ACTION_CHLD_1x, + BTHF_CLIENT_CALL_ACTION_CHLD_2x, + BTHF_CLIENT_CALL_ACTION_ATA, + BTHF_CLIENT_CALL_ACTION_CHUP, + BTHF_CLIENT_CALL_ACTION_BTRH_0, + BTHF_CLIENT_CALL_ACTION_BTRH_1, + BTHF_CLIENT_CALL_ACTION_BTRH_2, +} bthf_client_call_action_t; + +typedef enum { + BTHF_CLIENT_SERVICE_UNKNOWN = 0, + BTHF_CLIENT_SERVICE_VOICE, + BTHF_CLIENT_SERVICE_FAX +} bthf_client_subscriber_service_type_t; + +typedef enum { + BTHF_CLIENT_IN_BAND_RINGTONE_NOT_PROVIDED = 0, + BTHF_CLIENT_IN_BAND_RINGTONE_PROVIDED, +} bthf_client_in_band_ring_state_t; + +/* Peer features masks */ +#define BTHF_CLIENT_PEER_FEAT_3WAY 0x00000001 /* Three-way calling */ +#define BTHF_CLIENT_PEER_FEAT_ECNR 0x00000002 /* Echo cancellation and/or noise reduction */ +#define BTHF_CLIENT_PEER_FEAT_VREC 0x00000004 /* Voice recognition */ +#define BTHF_CLIENT_PEER_FEAT_INBAND 0x00000008 /* In-band ring tone */ +#define BTHF_CLIENT_PEER_FEAT_VTAG 0x00000010 /* Attach a phone number to a voice tag */ +#define BTHF_CLIENT_PEER_FEAT_REJECT 0x00000020 /* Ability to reject incoming call */ +#define BTHF_CLIENT_PEER_FEAT_ECS 0x00000040 /* Enhanced Call Status */ +#define BTHF_CLIENT_PEER_FEAT_ECC 0x00000080 /* Enhanced Call Control */ +#define BTHF_CLIENT_PEER_FEAT_EXTERR 0x00000100 /* Extended error codes */ +#define BTHF_CLIENT_PEER_FEAT_CODEC 0x00000200 /* Codec Negotiation */ + +/* Peer call handling features masks */ +#define BTHF_CLIENT_CHLD_FEAT_REL 0x00000001 /* 0 Release waiting call or held calls */ +#define BTHF_CLIENT_CHLD_FEAT_REL_ACC 0x00000002 /* 1 Release active calls and accept other + (waiting or held) cal */ +#define BTHF_CLIENT_CHLD_FEAT_REL_X 0x00000004 /* 1x Release specified active call only */ +#define BTHF_CLIENT_CHLD_FEAT_HOLD_ACC 0x00000008 /* 2 Active calls on hold and accept other + (waiting or held) call */ +#define BTHF_CLIENT_CHLD_FEAT_PRIV_X 0x00000010 /* 2x Request private mode with specified + call (put the rest on hold) */ +#define BTHF_CLIENT_CHLD_FEAT_MERGE 0x00000020 /* 3 Add held call to multiparty */ +#define BTHF_CLIENT_CHLD_FEAT_MERGE_DETACH 0x00000040 /* 4 Connect two calls and leave + (disconnect from) multiparty */ + +/** Callback for connection state change. + * state will have one of the values from BtHfConnectionState + * peer/chld_features are valid only for BTHF_CLIENT_CONNECTION_STATE_SLC_CONNECTED state + */ +typedef void (* bthf_client_connection_state_callback)(bthf_client_connection_state_t state, + unsigned int peer_feat, + unsigned int chld_feat, + bt_bdaddr_t *bd_addr); + +/** Callback for audio connection state change. + * state will have one of the values from BtHfAudioState + */ +typedef void (* bthf_client_audio_state_callback)(bthf_client_audio_state_t state, + bt_bdaddr_t *bd_addr); + +/** Callback for VR connection state change. + * state will have one of the values from BtHfVRState + */ +typedef void (* bthf_client_vr_cmd_callback)(bthf_client_vr_state_t state); + +/** Callback for network state change + */ +typedef void (* bthf_client_network_state_callback) (bthf_client_network_state_t state); + +/** Callback for network roaming status change + */ +typedef void (* bthf_client_network_roaming_callback) (bthf_client_service_type_t type); + +/** Callback for signal strength indication + */ +typedef void (* bthf_client_network_signal_callback) (int signal_strength); + +/** Callback for battery level indication + */ +typedef void (* bthf_client_battery_level_callback) (int battery_level); + +/** Callback for current operator name + */ +typedef void (* bthf_client_current_operator_callback) (const char *name); + +/** Callback for call indicator + */ +typedef void (* bthf_client_call_callback) (bthf_client_call_t call); + +/** Callback for callsetup indicator + */ +typedef void (* bthf_client_callsetup_callback) (bthf_client_callsetup_t callsetup); + +/** Callback for callheld indicator + */ +typedef void (* bthf_client_callheld_callback) (bthf_client_callheld_t callheld); + +/** Callback for response and hold + */ +typedef void (* bthf_client_resp_and_hold_callback) (bthf_client_resp_and_hold_t resp_and_hold); + +/** Callback for Calling Line Identification notification + * Will be called only when there is an incoming call and number is provided. + */ +typedef void (* bthf_client_clip_callback) (const char *number); + +/** + * Callback for Call Waiting notification + */ +typedef void (* bthf_client_call_waiting_callback) (const char *number); + +/** + * Callback for listing current calls. Can be called multiple time. + * If number is unknown NULL is passed. + */ +typedef void (*bthf_client_current_calls) (int index, bthf_client_call_direction_t dir, + bthf_client_call_state_t state, + bthf_client_call_mpty_type_t mpty, + const char *number); + +/** Callback for audio volume change + */ +typedef void (*bthf_client_volume_change_callback) (bthf_client_volume_type_t type, int volume); + +/** Callback for command complete event + * cme is valid only for BTHF_CLIENT_CMD_COMPLETE_ERROR_CME type + */ +typedef void (*bthf_client_cmd_complete_callback) (bthf_client_cmd_complete_t type, int cme); + +/** Callback for subscriber information + */ +typedef void (* bthf_client_subscriber_info_callback) (const char *name, + bthf_client_subscriber_service_type_t type); + +/** Callback for in-band ring tone settings + */ +typedef void (* bthf_client_in_band_ring_tone_callback) (bthf_client_in_band_ring_state_t state); + +/** + * Callback for requested number from AG + */ +typedef void (* bthf_client_last_voice_tag_number_callback) (const char *number); + +/** + * Callback for sending ring indication to app + */ +typedef void (* bthf_client_ring_indication_callback) (void); + +/** BT-HF callback structure. */ +typedef struct { + /** set to sizeof(BtHfClientCallbacks) */ + size_t size; + bthf_client_connection_state_callback connection_state_cb; + bthf_client_audio_state_callback audio_state_cb; + bthf_client_vr_cmd_callback vr_cmd_cb; + bthf_client_network_state_callback network_state_cb; + bthf_client_network_roaming_callback network_roaming_cb; + bthf_client_network_signal_callback network_signal_cb; + bthf_client_battery_level_callback battery_level_cb; + bthf_client_current_operator_callback current_operator_cb; + bthf_client_call_callback call_cb; + bthf_client_callsetup_callback callsetup_cb; + bthf_client_callheld_callback callheld_cb; + bthf_client_resp_and_hold_callback resp_and_hold_cb; + bthf_client_clip_callback clip_cb; + bthf_client_call_waiting_callback call_waiting_cb; + bthf_client_current_calls current_calls_cb; + bthf_client_volume_change_callback volume_change_cb; + bthf_client_cmd_complete_callback cmd_complete_cb; + bthf_client_subscriber_info_callback subscriber_info_cb; + bthf_client_in_band_ring_tone_callback in_band_ring_tone_cb; + bthf_client_last_voice_tag_number_callback last_voice_tag_number_callback; + bthf_client_ring_indication_callback ring_indication_cb; +} bthf_client_callbacks_t; + +/** Represents the standard BT-HF interface. */ +typedef struct { + + /** set to sizeof(BtHfClientInterface) */ + size_t size; + /** + * Register the BtHf callbacks + */ + bt_status_t (*init)(bthf_client_callbacks_t* callbacks); + + /** connect to audio gateway */ + bt_status_t (*connect)(bt_bdaddr_t *bd_addr); + + /** disconnect from audio gateway */ + bt_status_t (*disconnect)(bt_bdaddr_t *bd_addr); + + /** create an audio connection */ + bt_status_t (*connect_audio)(bt_bdaddr_t *bd_addr); + + /** close the audio connection */ + bt_status_t (*disconnect_audio)(bt_bdaddr_t *bd_addr); + + /** start voice recognition */ + bt_status_t (*start_voice_recognition)(void); + + /** stop voice recognition */ + bt_status_t (*stop_voice_recognition)(void); + + /** volume control */ + bt_status_t (*volume_control) (bthf_client_volume_type_t type, int volume); + + /** place a call with number a number + * if number is NULL last called number is called (aka re-dial)*/ + bt_status_t (*dial) (const char *number); + + /** place a call with number specified by location (speed dial) */ + bt_status_t (*dial_memory) (int location); + + /** perform specified call related action + * idx is limited only for enhanced call control related action + */ + bt_status_t (*handle_call_action) (bthf_client_call_action_t action, int idx); + + /** query list of current calls */ + bt_status_t (*query_current_calls) (void); + + /** query name of current selected operator */ + bt_status_t (*query_current_operator_name) (void); + + /** Retrieve subscriber information */ + bt_status_t (*retrieve_subscriber_info) (void); + + /** Send DTMF code*/ + bt_status_t (*send_dtmf) (char code); + + /** Request a phone number from AG corresponding to last voice tag recorded */ + bt_status_t (*request_last_voice_tag_number) (void); + + /** Closes the interface. */ + void (*cleanup)(void); + + /** Send AT Command. */ + bt_status_t (*send_at_cmd) (int cmd, int val1, int val2, const char *arg); +} bthf_client_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_HF_CLIENT_H */
diff --git a/android/hardware/bt_hh.h b/android/hardware/bt_hh.h new file mode 100644 index 0000000..dad9586 --- /dev/null +++ b/android/hardware/bt_hh.h
@@ -0,0 +1,185 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_HH_H +#define ANDROID_INCLUDE_BT_HH_H + +#include <stdint.h> + +__BEGIN_DECLS + +#define BTHH_MAX_DSC_LEN 884 + +/* HH connection states */ +typedef enum +{ + BTHH_CONN_STATE_CONNECTED = 0, + BTHH_CONN_STATE_CONNECTING, + BTHH_CONN_STATE_DISCONNECTED, + BTHH_CONN_STATE_DISCONNECTING, + BTHH_CONN_STATE_FAILED_MOUSE_FROM_HOST, + BTHH_CONN_STATE_FAILED_KBD_FROM_HOST, + BTHH_CONN_STATE_FAILED_TOO_MANY_DEVICES, + BTHH_CONN_STATE_FAILED_NO_BTHID_DRIVER, + BTHH_CONN_STATE_FAILED_GENERIC, + BTHH_CONN_STATE_UNKNOWN +} bthh_connection_state_t; + +typedef enum +{ + BTHH_OK = 0, + BTHH_HS_HID_NOT_READY, /* handshake error : device not ready */ + BTHH_HS_INVALID_RPT_ID, /* handshake error : invalid report ID */ + BTHH_HS_TRANS_NOT_SPT, /* handshake error : transaction not spt */ + BTHH_HS_INVALID_PARAM, /* handshake error : invalid paremter */ + BTHH_HS_ERROR, /* handshake error : unspecified HS error */ + BTHH_ERR, /* general BTA HH error */ + BTHH_ERR_SDP, /* SDP error */ + BTHH_ERR_PROTO, /* SET_Protocol error, + only used in BTA_HH_OPEN_EVT callback */ + BTHH_ERR_DB_FULL, /* device database full error, used */ + BTHH_ERR_TOD_UNSPT, /* type of device not supported */ + BTHH_ERR_NO_RES, /* out of system resources */ + BTHH_ERR_AUTH_FAILED, /* authentication fail */ + BTHH_ERR_HDL +}bthh_status_t; + +/* Protocol modes */ +typedef enum { + BTHH_REPORT_MODE = 0x00, + BTHH_BOOT_MODE = 0x01, + BTHH_UNSUPPORTED_MODE = 0xff +}bthh_protocol_mode_t; + +/* Report types */ +typedef enum { + BTHH_INPUT_REPORT = 1, + BTHH_OUTPUT_REPORT, + BTHH_FEATURE_REPORT +}bthh_report_type_t; + +typedef struct +{ + int attr_mask; + uint8_t sub_class; + uint8_t app_id; + int vendor_id; + int product_id; + int version; + uint8_t ctry_code; + int dl_len; + uint8_t dsc_list[BTHH_MAX_DSC_LEN]; +} bthh_hid_info_t; + +/** Callback for connection state change. + * state will have one of the values from bthh_connection_state_t + */ +typedef void (* bthh_connection_state_callback)(bt_bdaddr_t *bd_addr, bthh_connection_state_t state); + +/** Callback for vitual unplug api. + * the status of the vitual unplug + */ +typedef void (* bthh_virtual_unplug_callback)(bt_bdaddr_t *bd_addr, bthh_status_t hh_status); + +/** Callback for get hid info + * hid_info will contain attr_mask, sub_class, app_id, vendor_id, product_id, version, ctry_code, len + */ +typedef void (* bthh_hid_info_callback)(bt_bdaddr_t *bd_addr, bthh_hid_info_t hid_info); + +/** Callback for get protocol api. + * the protocol mode is one of the value from bthh_protocol_mode_t + */ +typedef void (* bthh_protocol_mode_callback)(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, bthh_protocol_mode_t mode); + +/** Callback for get/set_idle_time api. + */ +typedef void (* bthh_idle_time_callback)(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, int idle_rate); + + +/** Callback for get report api. + * if staus is ok rpt_data contains the report data + */ +typedef void (* bthh_get_report_callback)(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, uint8_t* rpt_data, int rpt_size); + +/** Callback for set_report/set_protocol api and if error + * occurs for get_report/get_protocol api. + */ +typedef void (* bthh_handshake_callback)(bt_bdaddr_t *bd_addr, bthh_status_t hh_status); + + +/** BT-HH callback structure. */ +typedef struct { + /** set to sizeof(BtHfCallbacks) */ + size_t size; + bthh_connection_state_callback connection_state_cb; + bthh_hid_info_callback hid_info_cb; + bthh_protocol_mode_callback protocol_mode_cb; + bthh_idle_time_callback idle_time_cb; + bthh_get_report_callback get_report_cb; + bthh_virtual_unplug_callback virtual_unplug_cb; + bthh_handshake_callback handshake_cb; + +} bthh_callbacks_t; + + + +/** Represents the standard BT-HH interface. */ +typedef struct { + + /** set to sizeof(BtHhInterface) */ + size_t size; + + /** + * Register the BtHh callbacks + */ + bt_status_t (*init)( bthh_callbacks_t* callbacks ); + + /** connect to hid device */ + bt_status_t (*connect)( bt_bdaddr_t *bd_addr); + + /** dis-connect from hid device */ + bt_status_t (*disconnect)( bt_bdaddr_t *bd_addr ); + + /** Virtual UnPlug (VUP) the specified HID device */ + bt_status_t (*virtual_unplug)(bt_bdaddr_t *bd_addr); + + /** Set the HID device descriptor for the specified HID device. */ + bt_status_t (*set_info)(bt_bdaddr_t *bd_addr, bthh_hid_info_t hid_info ); + + /** Get the HID proto mode. */ + bt_status_t (*get_protocol) (bt_bdaddr_t *bd_addr, bthh_protocol_mode_t protocolMode); + + /** Set the HID proto mode. */ + bt_status_t (*set_protocol)(bt_bdaddr_t *bd_addr, bthh_protocol_mode_t protocolMode); + + /** Send a GET_REPORT to HID device. */ + bt_status_t (*get_report)(bt_bdaddr_t *bd_addr, bthh_report_type_t reportType, uint8_t reportId, int bufferSize); + + /** Send a SET_REPORT to HID device. */ + bt_status_t (*set_report)(bt_bdaddr_t *bd_addr, bthh_report_type_t reportType, char* report); + + /** Send data to HID device. */ + bt_status_t (*send_data)(bt_bdaddr_t *bd_addr, char* data); + + /** Closes the interface. */ + void (*cleanup)( void ); + +} bthh_interface_t; +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_HH_H */ + +
diff --git a/android/hardware/bt_hl.h b/android/hardware/bt_hl.h new file mode 100644 index 0000000..bd29e3a --- /dev/null +++ b/android/hardware/bt_hl.h
@@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_HL_H +#define ANDROID_INCLUDE_BT_HL_H + +__BEGIN_DECLS + +/* HL connection states */ + +typedef enum +{ + BTHL_MDEP_ROLE_SOURCE, + BTHL_MDEP_ROLE_SINK +} bthl_mdep_role_t; + +typedef enum { + BTHL_APP_REG_STATE_REG_SUCCESS, + BTHL_APP_REG_STATE_REG_FAILED, + BTHL_APP_REG_STATE_DEREG_SUCCESS, + BTHL_APP_REG_STATE_DEREG_FAILED +} bthl_app_reg_state_t; + +typedef enum +{ + BTHL_CHANNEL_TYPE_RELIABLE, + BTHL_CHANNEL_TYPE_STREAMING, + BTHL_CHANNEL_TYPE_ANY +} bthl_channel_type_t; + + +/* HL connection states */ +typedef enum { + BTHL_CONN_STATE_CONNECTING, + BTHL_CONN_STATE_CONNECTED, + BTHL_CONN_STATE_DISCONNECTING, + BTHL_CONN_STATE_DISCONNECTED, + BTHL_CONN_STATE_DESTROYED +} bthl_channel_state_t; + +typedef struct +{ + bthl_mdep_role_t mdep_role; + int data_type; + bthl_channel_type_t channel_type; + const char *mdep_description; /* MDEP description to be used in the SDP (optional); null terminated */ +} bthl_mdep_cfg_t; + +typedef struct +{ + const char *application_name; + const char *provider_name; /* provider name to be used in the SDP (optional); null terminated */ + const char *srv_name; /* service name to be used in the SDP (optional); null terminated*/ + const char *srv_desp; /* service description to be used in the SDP (optional); null terminated */ + int number_of_mdeps; + bthl_mdep_cfg_t *mdep_cfg; /* Dynamic array */ +} bthl_reg_param_t; + +/** Callback for application registration status. + * state will have one of the values from bthl_app_reg_state_t + */ +typedef void (* bthl_app_reg_state_callback)(int app_id, bthl_app_reg_state_t state); + +/** Callback for channel connection state change. + * state will have one of the values from + * bthl_connection_state_t and fd (file descriptor) + */ +typedef void (* bthl_channel_state_callback)(int app_id, bt_bdaddr_t *bd_addr, int mdep_cfg_index, int channel_id, bthl_channel_state_t state, int fd); + +/** BT-HL callback structure. */ +typedef struct { + /** set to sizeof(bthl_callbacks_t) */ + size_t size; + bthl_app_reg_state_callback app_reg_state_cb; + bthl_channel_state_callback channel_state_cb; +} bthl_callbacks_t; + + +/** Represents the standard BT-HL interface. */ +typedef struct { + + /** set to sizeof(bthl_interface_t) */ + size_t size; + + /** + * Register the Bthl callbacks + */ + bt_status_t (*init)( bthl_callbacks_t* callbacks ); + + /** Register HL application */ + bt_status_t (*register_application) ( bthl_reg_param_t *p_reg_param, int *app_id); + + /** Unregister HL application */ + bt_status_t (*unregister_application) (int app_id); + + /** connect channel */ + bt_status_t (*connect_channel)(int app_id, bt_bdaddr_t *bd_addr, int mdep_cfg_index, int *channel_id); + + /** destroy channel */ + bt_status_t (*destroy_channel)(int channel_id); + + /** Close the Bthl callback **/ + void (*cleanup)(void); + +} bthl_interface_t; +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_HL_H */ + +
diff --git a/android/hardware/bt_mce.h b/android/hardware/bt_mce.h new file mode 100644 index 0000000..5d159b3 --- /dev/null +++ b/android/hardware/bt_mce.h
@@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef ANDROID_INCLUDE_BT_MCE_H +#define ANDROID_INCLUDE_BT_MCE_H + +__BEGIN_DECLS + +/** MAS instance description */ +typedef struct +{ + int id; + int scn; + int msg_types; + char *p_name; +} btmce_mas_instance_t; + +/** callback for get_remote_mas_instances */ +typedef void (*btmce_remote_mas_instances_callback)(bt_status_t status, bt_bdaddr_t *bd_addr, + int num_instances, btmce_mas_instance_t *instances); + +typedef struct { + /** set to sizeof(btmce_callbacks_t) */ + size_t size; + btmce_remote_mas_instances_callback remote_mas_instances_cb; +} btmce_callbacks_t; + +typedef struct { + /** set to size of this struct */ + size_t size; + + /** register BT MCE callbacks */ + bt_status_t (*init)(btmce_callbacks_t *callbacks); + + /** search for MAS instances on remote device */ + bt_status_t (*get_remote_mas_instances)(bt_bdaddr_t *bd_addr); +} btmce_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_MCE_H */
diff --git a/android/hardware/bt_pan.h b/android/hardware/bt_pan.h new file mode 100644 index 0000000..83e7949 --- /dev/null +++ b/android/hardware/bt_pan.h
@@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_PAN_H +#define ANDROID_INCLUDE_BT_PAN_H + +__BEGIN_DECLS + +#define BTPAN_ROLE_NONE 0 +#define BTPAN_ROLE_PANNAP 1 +#define BTPAN_ROLE_PANU 2 + +typedef enum { + BTPAN_STATE_CONNECTED = 0, + BTPAN_STATE_CONNECTING = 1, + BTPAN_STATE_DISCONNECTED = 2, + BTPAN_STATE_DISCONNECTING = 3 +} btpan_connection_state_t; + +typedef enum { + BTPAN_STATE_ENABLED = 0, + BTPAN_STATE_DISABLED = 1 +} btpan_control_state_t; + +/** +* Callback for pan connection state +*/ +typedef void (*btpan_connection_state_callback)(btpan_connection_state_t state, bt_status_t error, + const bt_bdaddr_t *bd_addr, int local_role, int remote_role); +typedef void (*btpan_control_state_callback)(btpan_control_state_t state, int local_role, + bt_status_t error, const char* ifname); + +typedef struct { + size_t size; + btpan_control_state_callback control_state_cb; + btpan_connection_state_callback connection_state_cb; +} btpan_callbacks_t; +typedef struct { + /** set to size of this struct*/ + size_t size; + /** + * Initialize the pan interface and register the btpan callbacks + */ + bt_status_t (*init)(const btpan_callbacks_t* callbacks); + /* + * enable the pan service by specified role. The result state of + * enabl will be returned by btpan_control_state_callback. when pan-nap is enabled, + * the state of connecting panu device will be notified by btpan_connection_state_callback + */ + bt_status_t (*enable)(int local_role); + /* + * get current pan local role + */ + int (*get_local_role)(void); + /** + * start bluetooth pan connection to the remote device by specified pan role. The result state will be + * returned by btpan_connection_state_callback + */ + bt_status_t (*connect)(const bt_bdaddr_t *bd_addr, int local_role, int remote_role); + /** + * stop bluetooth pan connection. The result state will be returned by btpan_connection_state_callback + */ + bt_status_t (*disconnect)(const bt_bdaddr_t *bd_addr); + + /** + * Cleanup the pan interface + */ + void (*cleanup)(void); + +} btpan_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_PAN_H */
diff --git a/android/hardware/bt_rc.h b/android/hardware/bt_rc.h new file mode 100644 index 0000000..c565c48 --- /dev/null +++ b/android/hardware/bt_rc.h
@@ -0,0 +1,296 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_RC_H +#define ANDROID_INCLUDE_BT_RC_H + +__BEGIN_DECLS + +/* Macros */ +#define BTRC_MAX_ATTR_STR_LEN 255 +#define BTRC_UID_SIZE 8 +#define BTRC_MAX_APP_SETTINGS 8 +#define BTRC_MAX_FOLDER_DEPTH 4 +#define BTRC_MAX_APP_ATTR_SIZE 16 +#define BTRC_MAX_ELEM_ATTR_SIZE 7 + +typedef uint8_t btrc_uid_t[BTRC_UID_SIZE]; + +typedef enum { + BTRC_FEAT_NONE = 0x00, /* AVRCP 1.0 */ + BTRC_FEAT_METADATA = 0x01, /* AVRCP 1.3 */ + BTRC_FEAT_ABSOLUTE_VOLUME = 0x02, /* Supports TG role and volume sync */ + BTRC_FEAT_BROWSE = 0x04, /* AVRCP 1.4 and up, with Browsing support */ +} btrc_remote_features_t; + +typedef enum { + BTRC_PLAYSTATE_STOPPED = 0x00, /* Stopped */ + BTRC_PLAYSTATE_PLAYING = 0x01, /* Playing */ + BTRC_PLAYSTATE_PAUSED = 0x02, /* Paused */ + BTRC_PLAYSTATE_FWD_SEEK = 0x03, /* Fwd Seek*/ + BTRC_PLAYSTATE_REV_SEEK = 0x04, /* Rev Seek*/ + BTRC_PLAYSTATE_ERROR = 0xFF, /* Error */ +} btrc_play_status_t; + +typedef enum { + BTRC_EVT_PLAY_STATUS_CHANGED = 0x01, + BTRC_EVT_TRACK_CHANGE = 0x02, + BTRC_EVT_TRACK_REACHED_END = 0x03, + BTRC_EVT_TRACK_REACHED_START = 0x04, + BTRC_EVT_PLAY_POS_CHANGED = 0x05, + BTRC_EVT_APP_SETTINGS_CHANGED = 0x08, +} btrc_event_id_t; + +typedef enum { + BTRC_NOTIFICATION_TYPE_INTERIM = 0, + BTRC_NOTIFICATION_TYPE_CHANGED = 1, +} btrc_notification_type_t; + +typedef enum { + BTRC_PLAYER_ATTR_EQUALIZER = 0x01, + BTRC_PLAYER_ATTR_REPEAT = 0x02, + BTRC_PLAYER_ATTR_SHUFFLE = 0x03, + BTRC_PLAYER_ATTR_SCAN = 0x04, +} btrc_player_attr_t; + +typedef enum { + BTRC_MEDIA_ATTR_TITLE = 0x01, + BTRC_MEDIA_ATTR_ARTIST = 0x02, + BTRC_MEDIA_ATTR_ALBUM = 0x03, + BTRC_MEDIA_ATTR_TRACK_NUM = 0x04, + BTRC_MEDIA_ATTR_NUM_TRACKS = 0x05, + BTRC_MEDIA_ATTR_GENRE = 0x06, + BTRC_MEDIA_ATTR_PLAYING_TIME = 0x07, +} btrc_media_attr_t; + +typedef enum { + BTRC_PLAYER_VAL_OFF_REPEAT = 0x01, + BTRC_PLAYER_VAL_SINGLE_REPEAT = 0x02, + BTRC_PLAYER_VAL_ALL_REPEAT = 0x03, + BTRC_PLAYER_VAL_GROUP_REPEAT = 0x04 +} btrc_player_repeat_val_t; + +typedef enum { + BTRC_PLAYER_VAL_OFF_SHUFFLE = 0x01, + BTRC_PLAYER_VAL_ALL_SHUFFLE = 0x02, + BTRC_PLAYER_VAL_GROUP_SHUFFLE = 0x03 +} btrc_player_shuffle_val_t; + +typedef enum { + BTRC_STS_BAD_CMD = 0x00, /* Invalid command */ + BTRC_STS_BAD_PARAM = 0x01, /* Invalid parameter */ + BTRC_STS_NOT_FOUND = 0x02, /* Specified parameter is wrong or not found */ + BTRC_STS_INTERNAL_ERR = 0x03, /* Internal Error */ + BTRC_STS_NO_ERROR = 0x04 /* Operation Success */ +} btrc_status_t; + +typedef struct { + uint8_t num_attr; + uint8_t attr_ids[BTRC_MAX_APP_SETTINGS]; + uint8_t attr_values[BTRC_MAX_APP_SETTINGS]; +} btrc_player_settings_t; + +typedef union +{ + btrc_play_status_t play_status; + btrc_uid_t track; /* queue position in NowPlaying */ + uint32_t song_pos; + btrc_player_settings_t player_setting; +} btrc_register_notification_t; + +typedef struct { + uint8_t id; /* can be attr_id or value_id */ + uint8_t text[BTRC_MAX_ATTR_STR_LEN]; +} btrc_player_setting_text_t; + +typedef struct { + uint32_t attr_id; + uint8_t text[BTRC_MAX_ATTR_STR_LEN]; +} btrc_element_attr_val_t; + +/** Callback for the controller's supported feautres */ +typedef void (* btrc_remote_features_callback)(bt_bdaddr_t *bd_addr, + btrc_remote_features_t features); + +/** Callback for play status request */ +typedef void (* btrc_get_play_status_callback)(); + +/** Callback for list player application attributes (Shuffle, Repeat,...) */ +typedef void (* btrc_list_player_app_attr_callback)(); + +/** Callback for list player application attributes (Shuffle, Repeat,...) */ +typedef void (* btrc_list_player_app_values_callback)(btrc_player_attr_t attr_id); + +/** Callback for getting the current player application settings value +** num_attr: specifies the number of attribute ids contained in p_attrs +*/ +typedef void (* btrc_get_player_app_value_callback) (uint8_t num_attr, btrc_player_attr_t *p_attrs); + +/** Callback for getting the player application settings attributes' text +** num_attr: specifies the number of attribute ids contained in p_attrs +*/ +typedef void (* btrc_get_player_app_attrs_text_callback) (uint8_t num_attr, btrc_player_attr_t *p_attrs); + +/** Callback for getting the player application settings values' text +** num_attr: specifies the number of value ids contained in p_vals +*/ +typedef void (* btrc_get_player_app_values_text_callback) (uint8_t attr_id, uint8_t num_val, uint8_t *p_vals); + +/** Callback for setting the player application settings values */ +typedef void (* btrc_set_player_app_value_callback) (btrc_player_settings_t *p_vals); + +/** Callback to fetch the get element attributes of the current song +** num_attr: specifies the number of attributes requested in p_attrs +*/ +typedef void (* btrc_get_element_attr_callback) (uint8_t num_attr, btrc_media_attr_t *p_attrs); + +/** Callback for register notification (Play state change/track change/...) +** param: Is only valid if event_id is BTRC_EVT_PLAY_POS_CHANGED +*/ +typedef void (* btrc_register_notification_callback) (btrc_event_id_t event_id, uint32_t param); + +/* AVRCP 1.4 Enhancements */ +/** Callback for volume change on CT +** volume: Current volume setting on the CT (0-127) +*/ +typedef void (* btrc_volume_change_callback) (uint8_t volume, uint8_t ctype); + +/** Callback for passthrough commands */ +typedef void (* btrc_passthrough_cmd_callback) (int id, int key_state); + +/** BT-RC Target callback structure. */ +typedef struct { + /** set to sizeof(BtRcCallbacks) */ + size_t size; + btrc_remote_features_callback remote_features_cb; + btrc_get_play_status_callback get_play_status_cb; + btrc_list_player_app_attr_callback list_player_app_attr_cb; + btrc_list_player_app_values_callback list_player_app_values_cb; + btrc_get_player_app_value_callback get_player_app_value_cb; + btrc_get_player_app_attrs_text_callback get_player_app_attrs_text_cb; + btrc_get_player_app_values_text_callback get_player_app_values_text_cb; + btrc_set_player_app_value_callback set_player_app_value_cb; + btrc_get_element_attr_callback get_element_attr_cb; + btrc_register_notification_callback register_notification_cb; + btrc_volume_change_callback volume_change_cb; + btrc_passthrough_cmd_callback passthrough_cmd_cb; +} btrc_callbacks_t; + +/** Represents the standard BT-RC AVRCP Target interface. */ +typedef struct { + + /** set to sizeof(BtRcInterface) */ + size_t size; + /** + * Register the BtRc callbacks + */ + bt_status_t (*init)( btrc_callbacks_t* callbacks ); + + /** Respose to GetPlayStatus request. Contains the current + ** 1. Play status + ** 2. Song duration/length + ** 3. Song position + */ + bt_status_t (*get_play_status_rsp)( btrc_play_status_t play_status, uint32_t song_len, uint32_t song_pos); + + /** Lists the support player application attributes (Shuffle/Repeat/...) + ** num_attr: Specifies the number of attributes contained in the pointer p_attrs + */ + bt_status_t (*list_player_app_attr_rsp)( int num_attr, btrc_player_attr_t *p_attrs); + + /** Lists the support player application attributes (Shuffle Off/On/Group) + ** num_val: Specifies the number of values contained in the pointer p_vals + */ + bt_status_t (*list_player_app_value_rsp)( int num_val, uint8_t *p_vals); + + /** Returns the current application attribute values for each of the specified attr_id */ + bt_status_t (*get_player_app_value_rsp)( btrc_player_settings_t *p_vals); + + /** Returns the application attributes text ("Shuffle"/"Repeat"/...) + ** num_attr: Specifies the number of attributes' text contained in the pointer p_attrs + */ + bt_status_t (*get_player_app_attr_text_rsp)( int num_attr, btrc_player_setting_text_t *p_attrs); + + /** Returns the application attributes text ("Shuffle"/"Repeat"/...) + ** num_attr: Specifies the number of attribute values' text contained in the pointer p_vals + */ + bt_status_t (*get_player_app_value_text_rsp)( int num_val, btrc_player_setting_text_t *p_vals); + + /** Returns the current songs' element attributes text ("Title"/"Album"/"Artist") + ** num_attr: Specifies the number of attributes' text contained in the pointer p_attrs + */ + bt_status_t (*get_element_attr_rsp)( uint8_t num_attr, btrc_element_attr_val_t *p_attrs); + + /** Response to set player attribute request ("Shuffle"/"Repeat") + ** rsp_status: Status of setting the player attributes for the current media player + */ + bt_status_t (*set_player_app_value_rsp)(btrc_status_t rsp_status); + + /* Response to the register notification request (Play state change/track change/...). + ** event_id: Refers to the event_id this notification change corresponds too + ** type: Response type - interim/changed + ** p_params: Based on the event_id, this parameter should be populated + */ + bt_status_t (*register_notification_rsp)(btrc_event_id_t event_id, + btrc_notification_type_t type, + btrc_register_notification_t *p_param); + + /* AVRCP 1.4 enhancements */ + + /**Send current volume setting to remote side. Support limited to SetAbsoluteVolume + ** This can be enhanced to support Relative Volume (AVRCP 1.0). + ** With RelateVolume, we will send VOLUME_UP/VOLUME_DOWN opposed to absolute volume level + ** volume: Should be in the range 0-127. bit7 is reseved and cannot be set + */ + bt_status_t (*set_volume)(uint8_t volume); + + /** Closes the interface. */ + void (*cleanup)( void ); +} btrc_interface_t; + + +typedef void (* btrc_passthrough_rsp_callback) (int id, int key_state); + +typedef void (* btrc_connection_state_callback) (bool state, bt_bdaddr_t *bd_addr); + +/** BT-RC Controller callback structure. */ +typedef struct { + /** set to sizeof(BtRcCallbacks) */ + size_t size; + btrc_passthrough_rsp_callback passthrough_rsp_cb; + btrc_connection_state_callback connection_state_cb; +} btrc_ctrl_callbacks_t; + +/** Represents the standard BT-RC AVRCP Controller interface. */ +typedef struct { + + /** set to sizeof(BtRcInterface) */ + size_t size; + /** + * Register the BtRc callbacks + */ + bt_status_t (*init)( btrc_ctrl_callbacks_t* callbacks ); + + /** send pass through command to target */ + bt_status_t (*send_pass_through_cmd) ( bt_bdaddr_t *bd_addr, uint8_t key_code, uint8_t key_state ); + + /** Closes the interface. */ + void (*cleanup)( void ); +} btrc_ctrl_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_RC_H */
diff --git a/android/hardware/bt_sock.h b/android/hardware/bt_sock.h new file mode 100644 index 0000000..a4aa046 --- /dev/null +++ b/android/hardware/bt_sock.h
@@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef ANDROID_INCLUDE_BT_SOCK_H +#define ANDROID_INCLUDE_BT_SOCK_H + +__BEGIN_DECLS + +#define BTSOCK_FLAG_ENCRYPT 1 +#define BTSOCK_FLAG_AUTH (1 << 1) + +typedef enum { + BTSOCK_RFCOMM = 1, + BTSOCK_SCO = 2, + BTSOCK_L2CAP = 3 +} btsock_type_t; + +/** Represents the standard BT SOCKET interface. */ +typedef struct { + short size; + bt_bdaddr_t bd_addr; + int channel; + int status; +} __attribute__((packed)) sock_connect_signal_t; + +typedef struct { + + /** set to size of this struct*/ + size_t size; + /** + * listen to a rfcomm uuid or channel. It returns the socket fd from which + * btsock_connect_signal can be read out when a remote device connected + */ + bt_status_t (*listen)(btsock_type_t type, const char* service_name, const uint8_t* service_uuid, int channel, int* sock_fd, int flags); + /* + * connect to a rfcomm uuid channel of remote device, It returns the socket fd from which + * the btsock_connect_signal and a new socket fd to be accepted can be read out when connected + */ + bt_status_t (*connect)(const bt_bdaddr_t *bd_addr, btsock_type_t type, const uint8_t* uuid, int channel, int* sock_fd, int flags); + +} btsock_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_SOCK_H */
diff --git a/android/hardware/hardware.c b/android/hardware/hardware.c new file mode 100644 index 0000000..4bd5eba --- /dev/null +++ b/android/hardware/hardware.c
@@ -0,0 +1,124 @@ +/* + * 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. + */ + +#include <hardware/hardware.h> + +#include <dlfcn.h> +#include <string.h> +#include <pthread.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> + +#define LOG_TAG "HAL" + +#define LOG_INFO " I" +#define LOG_WARN " W" +#define LOG_ERROR " E" +#define LOG_DEBUG " D" +#define ALOG(pri, tag, fmt, arg...) fprintf(stderr, tag pri": " fmt"\n", ##arg) + +#define info(fmt, arg...) ALOG(LOG_INFO, LOG_TAG, fmt, ##arg) +#define warn(fmt, arg...) ALOG(LOG_WARN, LOG_TAG, fmt, ##arg) +#define error(fmt, arg...) ALOG(LOG_ERROR, LOG_TAG, fmt, ##arg) + +/** + * Load the file defined by the variant and if successful + * return the dlopen handle and the hmi. + * @return 0 = success, !0 = failure. + */ +static int load(const char *id, + const char *path, + const struct hw_module_t **pHmi) +{ + int status; + void *handle; + struct hw_module_t *hmi; + const char *sym = HAL_MODULE_INFO_SYM_AS_STR; + + /* + * load the symbols resolving undefined symbols before + * dlopen returns. Since RTLD_GLOBAL is not or'd in with + * RTLD_NOW the external symbols will not be global + */ + handle = dlopen(path, RTLD_NOW); + if (handle == NULL) { + char const *err_str = dlerror(); + error("load: module=%s\n%s", path, err_str?err_str:"unknown"); + status = -EINVAL; + goto done; + } + + /* Get the address of the struct hal_module_info. */ + hmi = (struct hw_module_t *)dlsym(handle, sym); + if (hmi == NULL) { + error("load: couldn't find symbol %s", sym); + status = -EINVAL; + goto done; + } + + /* Check that the id matches */ + if (strcmp(id, hmi->id) != 0) { + error("load: id=%s != hmi->id=%s", id, hmi->id); + status = -EINVAL; + goto done; + } + + hmi->dso = handle; + + *pHmi = hmi; + + info("loaded HAL id=%s path=%s hmi=%p handle=%p", + id, path, *pHmi, handle); + + return 0; + +done: + hmi = NULL; + if (handle != NULL) { + dlclose(handle); + handle = NULL; + } + + return status; +} + +int hw_get_module_by_class(const char *class_id, const char *inst, + const struct hw_module_t **module) +{ + char path[PATH_MAX]; + char name[PATH_MAX]; + + if (inst) + snprintf(name, PATH_MAX, "%s.%s", class_id, inst); + else + snprintf(name, PATH_MAX, "%s", class_id); + + /* + * Here we rely on the fact that calling dlopen multiple times on + * the same .so will simply increment a refcount (and not load + * a new copy of the library). + * We also assume that dlopen() is thread-safe. + */ + snprintf(path, sizeof(path), "%s/%s.default.so", PLUGINDIR, name); + + return load(class_id, path, module); +} + +int hw_get_module(const char *id, const struct hw_module_t **module) +{ + return hw_get_module_by_class(id, NULL, module); +}
diff --git a/android/hardware/hardware.h b/android/hardware/hardware.h new file mode 100644 index 0000000..c7e8cc7 --- /dev/null +++ b/android/hardware/hardware.h
@@ -0,0 +1,227 @@ +/* + * 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. + */ + +#ifndef ANDROID_INCLUDE_HARDWARE_HARDWARE_H +#define ANDROID_INCLUDE_HARDWARE_HARDWARE_H + +#include <stdint.h> +#include <sys/cdefs.h> + +__BEGIN_DECLS + +/* + * Value for the hw_module_t.tag field + */ + +#define MAKE_TAG_CONSTANT(A,B,C,D) (((A) << 24) | ((B) << 16) | ((C) << 8) | (D)) + +#define HARDWARE_MODULE_TAG MAKE_TAG_CONSTANT('H', 'W', 'M', 'T') +#define HARDWARE_DEVICE_TAG MAKE_TAG_CONSTANT('H', 'W', 'D', 'T') + +#define HARDWARE_MAKE_API_VERSION(maj,min) \ + ((((maj) & 0xff) << 8) | ((min) & 0xff)) + +#define HARDWARE_MAKE_API_VERSION_2(maj,min,hdr) \ + ((((maj) & 0xff) << 24) | (((min) & 0xff) << 16) | ((hdr) & 0xffff)) +#define HARDWARE_API_VERSION_2_MAJ_MIN_MASK 0xffff0000 +#define HARDWARE_API_VERSION_2_HEADER_MASK 0x0000ffff + + +/* + * The current HAL API version. + * + * All module implementations must set the hw_module_t.hal_api_version field + * to this value when declaring the module with HAL_MODULE_INFO_SYM. + * + * Note that previous implementations have always set this field to 0. + * Therefore, libhardware HAL API will always consider versions 0.0 and 1.0 + * to be 100% binary compatible. + * + */ +#define HARDWARE_HAL_API_VERSION HARDWARE_MAKE_API_VERSION(1, 0) + +/* + * Helper macros for module implementors. + * + * The derived modules should provide convenience macros for supported + * versions so that implementations can explicitly specify module/device + * versions at definition time. + * + * Use this macro to set the hw_module_t.module_api_version field. + */ +#define HARDWARE_MODULE_API_VERSION(maj,min) HARDWARE_MAKE_API_VERSION(maj,min) +#define HARDWARE_MODULE_API_VERSION_2(maj,min,hdr) HARDWARE_MAKE_API_VERSION_2(maj,min,hdr) + +/* + * Use this macro to set the hw_device_t.version field + */ +#define HARDWARE_DEVICE_API_VERSION(maj,min) HARDWARE_MAKE_API_VERSION(maj,min) +#define HARDWARE_DEVICE_API_VERSION_2(maj,min,hdr) HARDWARE_MAKE_API_VERSION_2(maj,min,hdr) + +struct hw_module_t; +struct hw_module_methods_t; +struct hw_device_t; + +/** + * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM + * and the fields of this data structure must begin with hw_module_t + * followed by module specific information. + */ +typedef struct hw_module_t { + /** tag must be initialized to HARDWARE_MODULE_TAG */ + uint32_t tag; + + /** + * The API version of the implemented module. The module owner is + * responsible for updating the version when a module interface has + * changed. + * + * The derived modules such as gralloc and audio own and manage this field. + * The module user must interpret the version field to decide whether or + * not to inter-operate with the supplied module implementation. + * For example, SurfaceFlinger is responsible for making sure that + * it knows how to manage different versions of the gralloc-module API, + * and AudioFlinger must know how to do the same for audio-module API. + * + * The module API version should include a major and a minor component. + * For example, version 1.0 could be represented as 0x0100. This format + * implies that versions 0x0100-0x01ff are all API-compatible. + * + * In the future, libhardware will expose a hw_get_module_version() + * (or equivalent) function that will take minimum/maximum supported + * versions as arguments and would be able to reject modules with + * versions outside of the supplied range. + */ + uint16_t module_api_version; +#define version_major module_api_version + /** + * version_major/version_minor defines are supplied here for temporary + * source code compatibility. They will be removed in the next version. + * ALL clients must convert to the new version format. + */ + + /** + * The API version of the HAL module interface. This is meant to + * version the hw_module_t, hw_module_methods_t, and hw_device_t + * structures and definitions. + * + * The HAL interface owns this field. Module users/implementations + * must NOT rely on this value for version information. + * + * Presently, 0 is the only valid value. + */ + uint16_t hal_api_version; +#define version_minor hal_api_version + + /** Identifier of module */ + const char *id; + + /** Name of this module */ + const char *name; + + /** Author/owner/implementor of the module */ + const char *author; + + /** Modules methods */ + struct hw_module_methods_t* methods; + + /** module's dso */ + void* dso; + + /** padding to 128 bytes, reserved for future use */ + uint32_t reserved[32-7]; + +} hw_module_t; + +typedef struct hw_module_methods_t { + /** Open a specific device */ + int (*open)(const struct hw_module_t* module, const char* id, + struct hw_device_t** device); + +} hw_module_methods_t; + +/** + * Every device data structure must begin with hw_device_t + * followed by module specific public methods and attributes. + */ +typedef struct hw_device_t { + /** tag must be initialized to HARDWARE_DEVICE_TAG */ + uint32_t tag; + + /** + * Version of the module-specific device API. This value is used by + * the derived-module user to manage different device implementations. + * + * The module user is responsible for checking the module_api_version + * and device version fields to ensure that the user is capable of + * communicating with the specific module implementation. + * + * One module can support multiple devices with different versions. This + * can be useful when a device interface changes in an incompatible way + * but it is still necessary to support older implementations at the same + * time. One such example is the Camera 2.0 API. + * + * This field is interpreted by the module user and is ignored by the + * HAL interface itself. + */ + uint32_t version; + + /** reference to the module this device belongs to */ + struct hw_module_t* module; + + /** padding reserved for future use */ + uint32_t reserved[12]; + + /** Close this device */ + int (*close)(struct hw_device_t* device); + +} hw_device_t; + +/** + * Name of the hal_module_info + */ +#define HAL_MODULE_INFO_SYM HMI + +/** + * Name of the hal_module_info as a string + */ +#define HAL_MODULE_INFO_SYM_AS_STR "HMI" + +/** + * Get the module info associated with a module by id. + * + * @return: 0 == success, <0 == error and *module == NULL + */ +int hw_get_module(const char *id, const struct hw_module_t **module); + +/** + * Get the module info associated with a module instance by class 'class_id' + * and instance 'inst'. + * + * Some modules types necessitate multiple instances. For example audio supports + * multiple concurrent interfaces and thus 'audio' is the module class + * and 'primary' or 'a2dp' are module interfaces. This implies that the files + * providing these modules would be named audio.primary.<variant>.so and + * audio.a2dp.<variant>.so + * + * @return: 0 == success, <0 == error and *module == NULL + */ +int hw_get_module_by_class(const char *class_id, const char *inst, + const struct hw_module_t **module); + +__END_DECLS + +#endif /* ANDROID_INCLUDE_HARDWARE_HARDWARE_H */
diff --git a/android/health.c b/android/health.c new file mode 100644 index 0000000..eb02a64 --- /dev/null +++ b/android/health.c
@@ -0,0 +1,2047 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * + * 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 <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <glib.h> + +#include "btio/btio.h" +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" +#include "lib/l2cap.h" +#include "src/log.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/uuid-helper.h" +#include "src/sdp-client.h" +#include "profiles/health/mcap.h" + +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "utils.h" +#include "bluetooth.h" +#include "health.h" + +#define SVC_HINT_HEALTH 0x00 +#define HDP_VERSION 0x0101 +#define DATA_EXCHANGE_SPEC_11073 0x01 + +#define CHANNEL_TYPE_ANY 0x00 +#define CHANNEL_TYPE_RELIABLE 0x01 +#define CHANNEL_TYPE_STREAM 0x02 + +#define MDEP_ECHO 0x00 +#define MDEP_INITIAL 0x01 +#define MDEP_FINAL 0x7F + +static bdaddr_t adapter_addr; +static struct ipc *hal_ipc = NULL; +static struct queue *apps = NULL; +static struct mcap_instance *mcap = NULL; +static uint32_t record_id = 0; +static uint32_t record_state = 0; + +struct mdep_cfg { + uint8_t role; + uint16_t data_type; + uint8_t channel_type; + char *descr; + + uint8_t id; /* mdep id */ +}; + +struct health_device { + bdaddr_t dst; + uint16_t app_id; + + struct mcap_mcl *mcl; + + struct queue *channels; /* data channels */ + + uint16_t ccpsm; + uint16_t dcpsm; +}; + +struct health_channel { + uint8_t mdep_id; + uint8_t type; + + struct health_device *dev; + + uint8_t remote_mdep; + struct mcap_mdl *mdl; + bool mdl_conn; + uint16_t mdl_id; /* MDL ID */ + + uint16_t id; /* channel id */ +}; + +struct health_app { + char *app_name; + char *provider_name; + char *service_name; + char *service_descr; + uint8_t num_of_mdep; + struct queue *mdeps; + + uint16_t id; /* app id */ + struct queue *devices; +}; + +static void send_app_reg_notify(struct health_app *app, uint8_t state) +{ + struct hal_ev_health_app_reg_state ev; + + DBG(""); + + ev.id = app->id; + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_EV_HEALTH_APP_REG_STATE, sizeof(ev), &ev); +} + +static void send_channel_state_notify(struct health_channel *channel, + uint8_t state, int fd) +{ + struct hal_ev_health_channel_state ev; + + DBG(""); + + bdaddr2android(&channel->dev->dst, ev.bdaddr); + ev.app_id = channel->dev->app_id; + ev.mdep_index = channel->mdep_id - 1; + ev.channel_id = channel->id; + ev.channel_state = state; + + ipc_send_notif_with_fd(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_EV_HEALTH_CHANNEL_STATE, + sizeof(ev), &ev, fd); +} + +static void unref_mdl(struct health_channel *channel) +{ + if (!channel || !channel->mdl) + return; + + mcap_mdl_unref(channel->mdl); + channel->mdl = NULL; + channel->mdl_conn = false; +} + +static void free_health_channel(void *data) +{ + struct health_channel *channel = data; + int fd; + + DBG("channel %p", channel); + + if (!channel) + return; + + fd = mcap_mdl_get_fd(channel->mdl); + if (fd >= 0) + shutdown(fd, SHUT_RDWR); + + unref_mdl(channel); + free(channel); +} + +static void destroy_channel(void *data) +{ + struct health_channel *channel = data; + + if (!channel) + return; + + send_channel_state_notify(channel, HAL_HEALTH_CHANNEL_DESTROYED, -1); + queue_remove(channel->dev->channels, channel); + free_health_channel(channel); +} + +static void unref_mcl(struct health_device *dev) +{ + if (!dev || !dev->mcl) + return; + + mcap_close_mcl(dev->mcl, FALSE); + mcap_mcl_unref(dev->mcl); + dev->mcl = NULL; +} + +static void free_health_device(void *data) +{ + struct health_device *dev = data; + + if (!dev) + return; + + unref_mcl(dev); + queue_destroy(dev->channels, free_health_channel); + free(dev); +} + +static void free_mdep_cfg(void *data) +{ + struct mdep_cfg *cfg = data; + + if (!cfg) + return; + + free(cfg->descr); + free(cfg); +} + +static void free_health_app(void *data) +{ + struct health_app *app = data; + + if (!app) + return; + + free(app->app_name); + free(app->provider_name); + free(app->service_name); + free(app->service_descr); + queue_destroy(app->mdeps, free_mdep_cfg); + queue_destroy(app->devices, free_health_device); + free(app); +} + +static bool match_channel_by_mdl(const void *data, const void *user_data) +{ + const struct health_channel *channel = data; + const struct mcap_mdl *mdl = user_data; + + return channel->mdl == mdl; +} + +static bool match_channel_by_id(const void *data, const void *user_data) +{ + const struct health_channel *channel = data; + uint16_t channel_id = PTR_TO_INT(user_data); + + return channel->id == channel_id; +} + +static bool match_dev_by_mcl(const void *data, const void *user_data) +{ + const struct health_device *dev = data; + const struct mcap_mcl *mcl = user_data; + + return dev->mcl == mcl; +} + +static bool match_dev_by_addr(const void *data, const void *user_data) +{ + const struct health_device *dev = data; + const bdaddr_t *addr = user_data; + + return !bacmp(&dev->dst, addr); +} + +static bool match_channel_by_mdep_id(const void *data, const void *user_data) +{ + const struct health_channel *channel = data; + uint16_t mdep_id = PTR_TO_INT(user_data); + + return channel->mdep_id == mdep_id; +} + +static bool match_mdep_by_role(const void *data, const void *user_data) +{ + const struct mdep_cfg *mdep = data; + uint16_t role = PTR_TO_INT(user_data); + + return mdep->role == role; +} + +static bool match_mdep_by_id(const void *data, const void *user_data) +{ + const struct mdep_cfg *mdep = data; + uint16_t mdep_id = PTR_TO_INT(user_data); + + return mdep->id == mdep_id; +} + +static bool match_app_by_id(const void *data, const void *user_data) +{ + const struct health_app *app = data; + uint16_t app_id = PTR_TO_INT(user_data); + + return app->id == app_id; +} + +static struct health_channel *search_channel_by_id(uint16_t id) +{ + const struct queue_entry *apps_entry, *devices_entry; + struct health_app *app; + struct health_channel *channel; + struct health_device *dev; + + DBG(""); + + apps_entry = queue_get_entries(apps); + while (apps_entry) { + app = apps_entry->data; + devices_entry = queue_get_entries(app->devices); + while (devices_entry) { + dev = devices_entry->data; + channel = queue_find(dev->channels, match_channel_by_id, + INT_TO_PTR(id)); + + if (channel) + return channel; + + devices_entry = devices_entry->next; + } + + apps_entry = apps_entry->next; + } + + return NULL; +} + +static struct health_channel *search_channel_by_mdl(struct mcap_mdl *mdl) +{ + const struct queue_entry *apps_entry, *devices_entry; + struct health_app *app; + struct health_channel *channel; + struct health_device *dev; + + DBG(""); + + apps_entry = queue_get_entries(apps); + while (apps_entry) { + app = apps_entry->data; + devices_entry = queue_get_entries(app->devices); + while (devices_entry) { + dev = devices_entry->data; + channel = queue_find(dev->channels, + match_channel_by_mdl, mdl); + + if (channel) + return channel; + + devices_entry = devices_entry->next; + } + + apps_entry = apps_entry->next; + } + + return NULL; +} + +static struct health_device *search_dev_by_mcl(struct mcap_mcl *mcl) +{ + const struct queue_entry *apps_entry; + struct health_app *app; + struct health_device *dev; + + DBG(""); + + apps_entry = queue_get_entries(apps); + while (apps_entry) { + app = apps_entry->data; + + dev = queue_find(app->devices, match_dev_by_mcl, mcl); + + if (dev) + return dev; + + apps_entry = apps_entry->next; + } + + return NULL; +} + +static struct health_app *search_app_by_mdepid(uint8_t mdepid) +{ + const struct queue_entry *apps_entry; + struct health_app *app; + + DBG(""); + + apps_entry = queue_get_entries(apps); + while (apps_entry) { + app = apps_entry->data; + + if (queue_find(app->mdeps, match_mdep_by_id, + INT_TO_PTR(mdepid))) + return app; + + apps_entry = apps_entry->next; + } + + return NULL; +} + +static int register_service_protocols(sdp_record_t *rec, + struct health_app *app) +{ + uuid_t l2cap_uuid, mcap_c_uuid; + sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL; + sdp_list_t *access_proto_list = NULL; + sdp_data_t *psm = NULL, *mcap_ver = NULL; + uint32_t ccpsm; + uint16_t version = MCAP_VERSION; + GError *err = NULL; + int ret = -1; + + DBG(""); + + /* set l2cap information */ + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + l2cap_list = sdp_list_append(NULL, &l2cap_uuid); + if (!l2cap_list) + goto fail; + + ccpsm = mcap_get_ctrl_psm(mcap, &err); + if (err) + goto fail; + + psm = sdp_data_alloc(SDP_UINT16, &ccpsm); + if (!psm) + goto fail; + + if (!sdp_list_append(l2cap_list, psm)) + goto fail; + + proto_list = sdp_list_append(NULL, l2cap_list); + if (!proto_list) + goto fail; + + /* set mcap information */ + sdp_uuid16_create(&mcap_c_uuid, MCAP_CTRL_UUID); + mcap_list = sdp_list_append(NULL, &mcap_c_uuid); + if (!mcap_list) + goto fail; + + mcap_ver = sdp_data_alloc(SDP_UINT16, &version); + if (!mcap_ver) + goto fail; + + if (!sdp_list_append(mcap_list, mcap_ver)) + goto fail; + + if (!sdp_list_append(proto_list, mcap_list)) + goto fail; + + /* attach protocol information to service record */ + access_proto_list = sdp_list_append(NULL, proto_list); + if (!access_proto_list) + goto fail; + + sdp_set_access_protos(rec, access_proto_list); + ret = 0; + +fail: + sdp_list_free(l2cap_list, NULL); + sdp_list_free(mcap_list, NULL); + sdp_list_free(proto_list, NULL); + sdp_list_free(access_proto_list, NULL); + + if (psm) + sdp_data_free(psm); + + if (mcap_ver) + sdp_data_free(mcap_ver); + + if (err) + g_error_free(err); + + return ret; +} + +static int register_service_profiles(sdp_record_t *rec) +{ + int ret; + sdp_list_t *profile_list; + sdp_profile_desc_t hdp_profile; + + DBG(""); + + /* set hdp information */ + sdp_uuid16_create(&hdp_profile.uuid, HDP_SVCLASS_ID); + hdp_profile.version = HDP_VERSION; + profile_list = sdp_list_append(NULL, &hdp_profile); + if (!profile_list) + return -1; + + /* set profile descriptor list */ + ret = sdp_set_profile_descs(rec, profile_list); + sdp_list_free(profile_list, NULL); + + return ret; +} + +static int register_service_additional_protocols(sdp_record_t *rec, + struct health_app *app) +{ + int ret = -1; + uuid_t l2cap_uuid, mcap_d_uuid; + sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL; + sdp_list_t *access_proto_list = NULL; + sdp_data_t *psm = NULL; + uint32_t dcpsm; + GError *err = NULL; + + DBG(""); + + /* set l2cap information */ + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + l2cap_list = sdp_list_append(NULL, &l2cap_uuid); + if (!l2cap_list) + goto fail; + + dcpsm = mcap_get_data_psm(mcap, &err); + if (err) + goto fail; + + psm = sdp_data_alloc(SDP_UINT16, &dcpsm); + if (!psm) + goto fail; + + if (!sdp_list_append(l2cap_list, psm)) + goto fail; + + proto_list = sdp_list_append(NULL, l2cap_list); + if (!proto_list) + goto fail; + + /* set mcap information */ + sdp_uuid16_create(&mcap_d_uuid, MCAP_DATA_UUID); + mcap_list = sdp_list_append(NULL, &mcap_d_uuid); + if (!mcap_list) + goto fail; + + if (!sdp_list_append(proto_list, mcap_list)) + goto fail; + + /* attach protocol information to service record */ + access_proto_list = sdp_list_append(NULL, proto_list); + if (!access_proto_list) + goto fail; + + sdp_set_add_access_protos(rec, access_proto_list); + ret = 0; + +fail: + sdp_list_free(l2cap_list, NULL); + sdp_list_free(mcap_list, NULL); + sdp_list_free(proto_list, NULL); + sdp_list_free(access_proto_list, NULL); + + if (psm) + sdp_data_free(psm); + + if (err) + g_error_free(err); + + return ret; +} + +static sdp_list_t *mdeps_to_sdp_features(struct mdep_cfg *mdep) +{ + sdp_data_t *mdepid, *dtype = NULL, *role = NULL, *descr = NULL; + sdp_list_t *f_list = NULL; + + DBG(""); + + mdepid = sdp_data_alloc(SDP_UINT8, &mdep->id); + if (!mdepid) + return NULL; + + dtype = sdp_data_alloc(SDP_UINT16, &mdep->data_type); + if (!dtype) + goto fail; + + role = sdp_data_alloc(SDP_UINT8, &mdep->role); + if (!role) + goto fail; + + if (mdep->descr) { + descr = sdp_data_alloc(SDP_TEXT_STR8, mdep->descr); + if (!descr) + goto fail; + } + + f_list = sdp_list_append(NULL, mdepid); + if (!f_list) + goto fail; + + if (!sdp_list_append(f_list, dtype)) + goto fail; + + if (!sdp_list_append(f_list, role)) + goto fail; + + if (descr && !sdp_list_append(f_list, descr)) + goto fail; + + return f_list; + +fail: + sdp_list_free(f_list, NULL); + + if (mdepid) + sdp_data_free(mdepid); + + if (dtype) + sdp_data_free(dtype); + + if (role) + sdp_data_free(role); + + if (descr) + sdp_data_free(descr); + + return NULL; +} + +static void free_hdp_list(void *list) +{ + sdp_list_t *hdp_list = list; + + sdp_list_free(hdp_list, (sdp_free_func_t)sdp_data_free); +} + +static void register_features(void *data, void *user_data) +{ + struct mdep_cfg *mdep = data; + sdp_list_t **sup_features = user_data; + sdp_list_t *hdp_feature; + + DBG(""); + + hdp_feature = mdeps_to_sdp_features(mdep); + if (!hdp_feature) + return; + + if (!*sup_features) { + *sup_features = sdp_list_append(NULL, hdp_feature); + if (!*sup_features) + sdp_list_free(hdp_feature, + (sdp_free_func_t)sdp_data_free); + } else if (!sdp_list_append(*sup_features, hdp_feature)) { + sdp_list_free(hdp_feature, + (sdp_free_func_t)sdp_data_free); + } +} + +static int register_service_sup_features(sdp_record_t *rec, + struct health_app *app) +{ + sdp_list_t *sup_features = NULL; + + DBG(""); + + queue_foreach(app->mdeps, register_features, &sup_features); + if (!sup_features) + return -1; + + if (sdp_set_supp_feat(rec, sup_features) < 0) { + sdp_list_free(sup_features, free_hdp_list); + return -1; + } + + sdp_list_free(sup_features, free_hdp_list); + return 0; +} + +static int register_data_exchange_spec(sdp_record_t *rec) +{ + sdp_data_t *spec; + uint8_t data_spec = DATA_EXCHANGE_SPEC_11073; + /* As of now only 11073 is supported, so we set it as default */ + + DBG(""); + + spec = sdp_data_alloc(SDP_UINT8, &data_spec); + if (!spec) + return -1; + + if (sdp_attr_add(rec, SDP_ATTR_DATA_EXCHANGE_SPEC, spec) < 0) { + sdp_data_free(spec); + return -1; + } + + return 0; +} + +static int register_mcap_features(sdp_record_t *rec) +{ + sdp_data_t *mcap_proc; + uint8_t mcap_sup_proc = MCAP_SUP_PROC; + + DBG(""); + + mcap_proc = sdp_data_alloc(SDP_UINT8, &mcap_sup_proc); + if (!mcap_proc) + return -1; + + if (sdp_attr_add(rec, SDP_ATTR_MCAP_SUPPORTED_PROCEDURES, + mcap_proc) < 0) { + sdp_data_free(mcap_proc); + return -1; + } + + return 0; +} + +static int set_sdp_services_uuid(sdp_record_t *rec, uint8_t role) +{ + uuid_t source, sink; + sdp_list_t *list = NULL; + + sdp_uuid16_create(&sink, HDP_SINK_SVCLASS_ID); + sdp_uuid16_create(&source, HDP_SOURCE_SVCLASS_ID); + sdp_get_service_classes(rec, &list); + + switch (role) { + case HAL_HEALTH_MDEP_ROLE_SOURCE: + if (!sdp_list_find(list, &source, sdp_uuid_cmp)) + list = sdp_list_append(list, &source); + break; + case HAL_HEALTH_MDEP_ROLE_SINK: + if (!sdp_list_find(list, &sink, sdp_uuid_cmp)) + list = sdp_list_append(list, &sink); + break; + } + + if (sdp_set_service_classes(rec, list) < 0) { + sdp_list_free(list, NULL); + return -1; + } + + sdp_list_free(list, NULL); + + return 0; +} + +static int update_sdp_record(struct health_app *app) +{ + sdp_record_t *rec; + uint8_t role; + + DBG(""); + + if (record_id > 0) { + bt_adapter_remove_record(record_id); + record_id = 0; + } + + rec = sdp_record_alloc(); + if (!rec) + return -1; + + role = HAL_HEALTH_MDEP_ROLE_SOURCE; + if (queue_find(app->mdeps, match_mdep_by_role, INT_TO_PTR(role))) + set_sdp_services_uuid(rec, role); + + role = HAL_HEALTH_MDEP_ROLE_SINK; + if (queue_find(app->mdeps, match_mdep_by_role, INT_TO_PTR(role))) + set_sdp_services_uuid(rec, role); + + sdp_set_info_attr(rec, app->service_name, app->provider_name, + app->service_descr); + + if (register_service_protocols(rec, app) < 0) + goto fail; + + if (register_service_profiles(rec) < 0) + goto fail; + + if (register_service_additional_protocols(rec, app) < 0) + goto fail; + + if (register_service_sup_features(rec, app) < 0) + goto fail; + + if (register_data_exchange_spec(rec) < 0) + goto fail; + + if (register_mcap_features(rec) < 0) + goto fail; + + if (sdp_set_record_state(rec, record_state++) < 0) + goto fail; + + if (bt_adapter_add_record(rec, SVC_HINT_HEALTH) < 0) { + error("health: Failed to register HEALTH record"); + goto fail; + } + + record_id = rec->handle; + + return 0; + +fail: + sdp_record_free(rec); + + return -1; +} + +static struct health_app *create_health_app(const char *app_name, + const char *provider, const char *srv_name, + const char *srv_descr, uint8_t mdeps) +{ + struct health_app *app; + static unsigned int app_id = 1; + + DBG(""); + + app = new0(struct health_app, 1); + app->id = app_id++; + app->num_of_mdep = mdeps; + app->app_name = strdup(app_name); + + if (provider) { + app->provider_name = strdup(provider); + if (!app->provider_name) + goto fail; + } + + if (srv_name) { + app->service_name = strdup(srv_name); + if (!app->service_name) + goto fail; + } + + if (srv_descr) { + app->service_descr = strdup(srv_descr); + if (!app->service_descr) + goto fail; + } + + app->mdeps = queue_new(); + app->devices = queue_new(); + + return app; + +fail: + free_health_app(app); + return NULL; +} + +static void bt_health_register_app(const void *buf, uint16_t len) +{ + const struct hal_cmd_health_reg_app *cmd = buf; + struct hal_rsp_health_reg_app rsp; + struct health_app *app; + uint16_t off; + uint16_t app_name_len, provider_len, srv_name_len, srv_descr_len; + char *app_name, *provider = NULL, *srv_name = NULL, *srv_descr = NULL; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->len || + cmd->app_name_off > cmd->provider_name_off || + cmd->provider_name_off > cmd->service_name_off || + cmd->service_name_off > cmd->service_descr_off || + cmd->service_descr_off > cmd->len) { + error("health: Invalid register app command, terminating"); + raise(SIGTERM); + return; + } + + app_name = (char *) cmd->data; + app_name_len = cmd->provider_name_off - cmd->app_name_off; + + off = app_name_len; + provider_len = cmd->service_name_off - off; + if (provider_len > 0) + provider = (char *) cmd->data + off; + + off += provider_len; + srv_name_len = cmd->service_descr_off - off; + if (srv_name_len > 0) + srv_name = (char *) cmd->data + off; + + off += srv_name_len; + srv_descr_len = cmd->len - off; + if (srv_descr_len > 0) + srv_descr = (char *) cmd->data + off; + + app = create_health_app(app_name, provider, srv_name, srv_descr, + cmd->num_of_mdep); + if (!app) + goto fail; + + queue_push_tail(apps, app); + + rsp.app_id = app->id; + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_REG_APP, + sizeof(rsp), &rsp, -1); + return; + +fail: + free_health_app(app); + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_MDEP, + HAL_STATUS_FAILED); +} + +static uint8_t android2channel_type(uint8_t type) +{ + switch (type) { + case HAL_HEALTH_CHANNEL_TYPE_RELIABLE: + return CHANNEL_TYPE_RELIABLE; + case HAL_HEALTH_CHANNEL_TYPE_STREAMING: + return CHANNEL_TYPE_STREAM; + default: + return CHANNEL_TYPE_ANY; + } +} + +static void bt_health_mdep_cfg_data(const void *buf, uint16_t len) +{ + const struct hal_cmd_health_mdep *cmd = buf; + struct health_app *app; + struct mdep_cfg *mdep = NULL; + uint8_t status; + + DBG(""); + + app = queue_find(apps, match_app_by_id, INT_TO_PTR(cmd->app_id)); + if (!app) { + status = HAL_STATUS_INVALID; + goto fail; + } + + mdep = new0(struct mdep_cfg, 1); + mdep->role = cmd->role; + mdep->data_type = cmd->data_type; + mdep->channel_type = android2channel_type(cmd->channel_type); + mdep->id = queue_length(app->mdeps) + 1; + + if (cmd->descr_len > 0) { + mdep->descr = malloc0(cmd->descr_len); + memcpy(mdep->descr, cmd->descr, cmd->descr_len); + } + + queue_push_tail(app->mdeps, mdep); + + if (app->num_of_mdep != queue_length(app->mdeps)) + goto send_rsp; + + /* add sdp record from app configuration data */ + /* + * TODO: Check what to be done if mupltple applications are trying to + * register with different role and different configurations. + * 1) Does device supports SOURCE and SINK at the same time ? + * 2) Does it require different SDP records or one record with + * multile MDEP configurations ? + */ + if (update_sdp_record(app) < 0) { + error("health: HDP SDP record preparation failed"); + status = HAL_STATUS_FAILED; + goto fail; + } + + send_app_reg_notify(app, HAL_HEALTH_APP_REG_SUCCESS); + +send_rsp: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_MDEP, + HAL_STATUS_SUCCESS); + return; + +fail: + if (status != HAL_STATUS_SUCCESS) { + free_mdep_cfg(mdep); + queue_remove(apps, app); + free_health_app(app); + } + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, HAL_OP_HEALTH_MDEP, + status); +} + +static void bt_health_unregister_app(const void *buf, uint16_t len) +{ + const struct hal_cmd_health_unreg_app *cmd = buf; + struct health_app *app; + + DBG(""); + + app = queue_remove_if(apps, match_app_by_id, INT_TO_PTR(cmd->app_id)); + if (!app) { + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_UNREG_APP, HAL_STATUS_INVALID); + return; + } + + send_app_reg_notify(app, HAL_HEALTH_APP_DEREG_SUCCESS); + + if (record_id > 0) { + bt_adapter_remove_record(record_id); + record_id = 0; + } + + free_health_app(app); + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_UNREG_APP, HAL_STATUS_SUCCESS); +} + +static int get_prot_desc_entry(sdp_data_t *entry, int type, guint16 *val) +{ + sdp_data_t *iter; + int proto; + + if (!entry || !SDP_IS_SEQ(entry->dtd)) + return -1; + + iter = entry->val.dataseq; + if (!(iter->dtd & SDP_UUID_UNSPEC)) + return -1; + + proto = sdp_uuid_to_proto(&iter->val.uuid); + if (proto != type) + return -1; + + if (!val) + return 0; + + iter = iter->next; + if (iter->dtd != SDP_UINT16) + return -1; + + *val = iter->val.uint16; + + return 0; +} + +static int get_prot_desc_list(const sdp_record_t *rec, uint16_t *psm, + uint16_t *version) +{ + sdp_data_t *pdl, *p0, *p1; + + if (!psm && !version) + return -1; + + pdl = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST); + if (!pdl || !SDP_IS_SEQ(pdl->dtd)) + return -1; + + p0 = pdl->val.dataseq; + if (get_prot_desc_entry(p0, L2CAP_UUID, psm) < 0) + return -1; + + p1 = p0->next; + if (get_prot_desc_entry(p1, MCAP_CTRL_UUID, version) < 0) + return -1; + + return 0; +} + +static int get_ccpsm(sdp_list_t *recs, uint16_t *ccpsm) +{ + sdp_list_t *l; + + for (l = recs; l; l = l->next) { + sdp_record_t *rec = l->data; + + if (!get_prot_desc_list(rec, ccpsm, NULL)) + return 0; + } + + return -1; +} + +static int get_add_prot_desc_list(const sdp_record_t *rec, uint16_t *psm) +{ + sdp_data_t *pdl, *p0, *p1; + + if (!psm) + return -1; + + pdl = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST); + if (!pdl || pdl->dtd != SDP_SEQ8) + return -1; + + pdl = pdl->val.dataseq; + if (pdl->dtd != SDP_SEQ8) + return -1; + + p0 = pdl->val.dataseq; + + if (get_prot_desc_entry(p0, L2CAP_UUID, psm) < 0) + return -1; + + p1 = p0->next; + if (get_prot_desc_entry(p1, MCAP_DATA_UUID, NULL) < 0) + return -1; + + return 0; +} + +static int get_dcpsm(sdp_list_t *recs, uint16_t *dcpsm) +{ + sdp_list_t *l; + + for (l = recs; l; l = l->next) { + sdp_record_t *rec = l->data; + + if (!get_add_prot_desc_list(rec, dcpsm)) + return 0; + } + + return -1; +} + +static int send_echo_data(int sock, const void *buf, uint32_t size) +{ + const uint8_t *buf_b = buf; + uint32_t sent = 0; + + while (sent < size) { + int n = write(sock, buf_b + sent, size - sent); + if (n < 0) + return -1; + sent += n; + } + + return 0; +} + +static gboolean serve_echo(GIOChannel *io, GIOCondition cond, gpointer data) +{ + struct health_channel *channel = data; + uint8_t buf[MCAP_DC_MTU]; + int fd, len, ret; + + DBG("channel %p", channel); + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + DBG("Error condition on channel"); + return FALSE; + } + + fd = g_io_channel_unix_get_fd(io); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + DBG("Error reading ECHO"); + return FALSE; + } + + ret = send_echo_data(fd, buf, len); + if (ret != len) + DBG("Error sending ECHO back"); + + return FALSE; +} + +static void mcap_mdl_connected_cb(struct mcap_mdl *mdl, void *data) +{ + struct health_channel *channel = data; + int fd; + + DBG("Data channel connected: mdl %p channel %p", mdl, channel); + + if (!channel) { + channel = search_channel_by_mdl(mdl); + if (!channel) { + error("health: channel data does not exist"); + return; + } + } + + if (!channel->mdl) + channel->mdl = mcap_mdl_ref(mdl); + + fd = mcap_mdl_get_fd(channel->mdl); + if (fd < 0) { + error("health: error retrieving fd"); + goto fail; + } + + if (channel->mdep_id == MDEP_ECHO) { + GIOChannel *io; + + io = g_io_channel_unix_new(fd); + g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN, + serve_echo, channel); + g_io_channel_unref(io); + + return; + } + + info("health: MDL connected"); + send_channel_state_notify(channel, HAL_HEALTH_CHANNEL_CONNECTED, fd); + + return; +fail: + /* TODO: mcap_mdl_abort */ + destroy_channel(channel); +} + +static void mcap_mdl_closed_cb(struct mcap_mdl *mdl, void *data) +{ + struct health_channel *channel = data; + + info("health: MDL closed"); + + if (!channel) + return; + + channel->mdl_conn = false; +} + +static void mcap_mdl_deleted_cb(struct mcap_mdl *mdl, void *data) +{ + struct health_channel *channel; + + info("health: MDL deleted"); + + channel = search_channel_by_mdl(mdl); + if (!channel) + return; + + DBG("channel %p mdl %p", channel, mdl); + destroy_channel(channel); +} + +static void mcap_mdl_aborted_cb(struct mcap_mdl *mdl, void *data) +{ + DBG("Not Implemeneted"); +} + +static struct health_device *create_device(struct health_app *app, + const uint8_t *addr) +{ + struct health_device *dev; + + /* create device and push it to devices queue */ + dev = new0(struct health_device, 1); + + android2bdaddr(addr, &dev->dst); + dev->channels = queue_new(); + dev->app_id = app->id; + + queue_push_tail(app->devices, dev); + + return dev; +} + +static struct health_device *get_device(struct health_app *app, + const uint8_t *addr) +{ + struct health_device *dev; + bdaddr_t bdaddr; + + android2bdaddr(addr, &bdaddr); + dev = queue_find(app->devices, match_dev_by_addr, &bdaddr); + if (dev) + return dev; + + return create_device(app, addr); +} + +static struct health_channel *create_channel(struct health_app *app, + uint8_t mdep_index, + struct health_device *dev) +{ + struct mdep_cfg *mdep; + struct health_channel *channel; + static unsigned int channel_id = 1; + + DBG("mdep %u", mdep_index); + + if (!dev || !app) + return NULL; + + mdep = queue_find(app->mdeps, match_mdep_by_id, INT_TO_PTR(mdep_index)); + if (!mdep) { + if (mdep_index == MDEP_ECHO) { + mdep = new0(struct mdep_cfg, 1); + + /* Leave other configuration zeroes */ + mdep->id = MDEP_ECHO; + + queue_push_tail(app->mdeps, mdep); + } else { + return NULL; + } + } + + /* create channel and push it to device */ + channel = new0(struct health_channel, 1); + channel->mdep_id = mdep->id; + channel->type = mdep->channel_type; + channel->id = channel_id++; + channel->dev = dev; + + queue_push_tail(dev->channels, channel); + + return channel; +} + +static struct health_channel *connect_channel(struct health_app *app, + struct mcap_mcl *mcl, + uint8_t mdepid) +{ + struct health_device *device; + bdaddr_t addr; + + DBG("app %p mdepid %u", app, mdepid); + + mcap_mcl_get_addr(mcl, &addr); + + if (!app) { + DBG("No app found for mdepid %u", mdepid); + return NULL; + } + + device = get_device(app, (uint8_t *) &addr); + + return create_channel(app, mdepid, device); +} + +static uint8_t conf_to_l2cap(uint8_t conf) +{ + return conf == CHANNEL_TYPE_STREAM ? L2CAP_MODE_STREAMING : + L2CAP_MODE_ERTM; +} + +static uint8_t mcap_mdl_conn_req_cb(struct mcap_mcl *mcl, uint8_t mdepid, + uint16_t mdlid, uint8_t *conf, void *data) +{ + GError *gerr = NULL; + struct health_channel *channel; + struct health_app *app; + struct mdep_cfg *mdep; + + DBG("Data channel request: mdepid %u mdlid %u conf %u", + mdepid, mdlid, *conf); + + if (mdepid == MDEP_ECHO) + /* For echo service take last app */ + app = queue_peek_tail(apps); + else + app = search_app_by_mdepid(mdepid); + + if (!app) + return MCAP_MDL_BUSY; + + channel = connect_channel(app, mcl, mdepid); + if (!channel) + return MCAP_MDL_BUSY; + + /* Channel is assigned here after creation */ + mcl->cb->user_data = channel; + + if (mdepid == MDEP_ECHO) { + switch (*conf) { + case CHANNEL_TYPE_ANY: + *conf = CHANNEL_TYPE_RELIABLE; + break; + case CHANNEL_TYPE_RELIABLE: + break; + case CHANNEL_TYPE_STREAM: + return MCAP_CONFIGURATION_REJECTED; + default: + /* + * Special case defined in HDP spec 3.4. + * When an invalid configuration is received we shall + * close the MCL when we are still processing the + * callback. + */ + /* TODO close device */ + return MCAP_CONFIGURATION_REJECTED; /* not processed */ + } + + if (!mcap_set_data_chan_mode(mcap, L2CAP_MODE_ERTM, &gerr)) { + error("Error: %s", gerr->message); + g_error_free(gerr); + return MCAP_MDL_BUSY; + } + + /* TODO: Create channel */ + + return MCAP_SUCCESS; + } + + mdep = queue_find(app->mdeps, match_mdep_by_id, INT_TO_PTR(mdepid)); + if (!mdep) + return MCAP_MDL_BUSY; + + switch (*conf) { + case CHANNEL_TYPE_ANY: + if (mdep->role == HAL_HEALTH_MDEP_ROLE_SINK) { + return MCAP_CONFIGURATION_REJECTED; + } else { + if (queue_length(channel->dev->channels) <= 1) + *conf = CHANNEL_TYPE_RELIABLE; + else + *conf = CHANNEL_TYPE_STREAM; + } + break; + case CHANNEL_TYPE_STREAM: + if (mdep->role == HAL_HEALTH_MDEP_ROLE_SOURCE) + return MCAP_CONFIGURATION_REJECTED; + break; + case CHANNEL_TYPE_RELIABLE: + if (mdep->role == HAL_HEALTH_MDEP_ROLE_SOURCE) + return MCAP_CONFIGURATION_REJECTED; + break; + default: + /* + * Special case defined in HDP spec 3.4. When an invalid + * configuration is received we shall close the MCL when + * we are still processing the callback. + */ + /* TODO: close device */ + return MCAP_CONFIGURATION_REJECTED; /* not processed */ + } + + if (!mcap_set_data_chan_mode(mcap, conf_to_l2cap(*conf), &gerr)) { + error("health: error setting L2CAP mode: %s", gerr->message); + g_error_free(gerr); + return MCAP_MDL_BUSY; + } + + return MCAP_SUCCESS; +} + +static uint8_t mcap_mdl_reconn_req_cb(struct mcap_mdl *mdl, void *data) +{ + struct health_channel *channel; + GError *err = NULL; + + DBG(""); + + channel = search_channel_by_mdl(mdl); + if (!channel) { + error("health: channel data does not exist"); + return MCAP_UNSPECIFIED_ERROR; + } + + if (!mcap_set_data_chan_mode(mcap, + conf_to_l2cap(channel->type), &err)) { + error("health: %s", err->message); + g_error_free(err); + return MCAP_MDL_BUSY; + } + + return MCAP_SUCCESS; +} + +static void connect_mdl_cb(struct mcap_mdl *mdl, GError *gerr, gpointer data) +{ + struct health_channel *channel = data; + int fd; + + DBG(""); + + if (gerr) { + error("health: error connecting to MDL %s", gerr->message); + goto fail; + } + + fd = mcap_mdl_get_fd(channel->mdl); + if (fd < 0) { + error("health: error retrieving fd"); + goto fail; + } + + info("health: MDL connected"); + channel->mdl_conn = true; + + /* first data channel should be reliable data channel */ + if (!queue_length(channel->dev->channels)) + if (channel->type != CHANNEL_TYPE_RELIABLE) + goto fail; + + send_channel_state_notify(channel, HAL_HEALTH_CHANNEL_CONNECTED, fd); + + return; + +fail: + /* TODO: mcap_mdl_abort */ + destroy_channel(channel); +} + +static void reconnect_mdl_cb(struct mcap_mdl *mdl, GError *gerr, gpointer data) +{ + struct health_channel *channel = data; + uint8_t mode; + GError *err = NULL; + + DBG(""); + + if (gerr) { + error("health: error reconnecting to MDL %s", gerr->message); + goto fail; + } + + channel->mdl_id = mcap_mdl_get_mdlid(mdl); + + if (channel->type == CHANNEL_TYPE_RELIABLE) + mode = L2CAP_MODE_ERTM; + else + mode = L2CAP_MODE_STREAMING; + + if (!mcap_connect_mdl(channel->mdl, mode, channel->dev->dcpsm, + connect_mdl_cb, channel, + NULL, &err)) { + error("health: error connecting to mdl"); + g_error_free(err); + goto fail; + } + + return; + +fail: + /* TODO: mcap_mdl_abort */ + destroy_channel(channel); +} + +static int reconnect_mdl(struct health_channel *channel) +{ + GError *gerr = NULL; + + DBG(""); + + if (!channel) + return -1; + + if (!mcap_reconnect_mdl(channel->mdl, reconnect_mdl_cb, channel, + NULL, &gerr)){ + error("health: reconnect failed %s", gerr->message); + destroy_channel(channel); + } + + return 0; +} + +static void create_mdl_cb(struct mcap_mdl *mdl, uint8_t type, GError *gerr, + gpointer data) +{ + struct health_channel *channel = data; + uint8_t mode; + GError *err = NULL; + + DBG(""); + if (gerr) { + error("health: error creating MDL %s", gerr->message); + goto fail; + } + + if (channel->type == CHANNEL_TYPE_ANY && type != CHANNEL_TYPE_ANY) + channel->type = type; + + /* + * if requested channel type is not same as preferred + * channel type from remote device, then abort the connection. + */ + if (channel->type != type) { + /* TODO: abort mdl */ + error("health: channel type requested %d preferred %d not same", + channel->type, type); + goto fail; + } + + if (!channel->mdl) + channel->mdl = mcap_mdl_ref(mdl); + + channel->type = type; + channel->mdl_id = mcap_mdl_get_mdlid(mdl); + + if (channel->type == CHANNEL_TYPE_RELIABLE) + mode = L2CAP_MODE_ERTM; + else + mode = L2CAP_MODE_STREAMING; + + if (!mcap_connect_mdl(channel->mdl, mode, channel->dev->dcpsm, + connect_mdl_cb, channel, + NULL, &err)) { + error("health: error connecting to mdl"); + g_error_free(err); + goto fail; + } + + return; + +fail: + destroy_channel(channel); +} + +static bool check_role(uint8_t rec_role, uint8_t app_role) +{ + if ((rec_role == HAL_HEALTH_MDEP_ROLE_SINK && + app_role == HAL_HEALTH_MDEP_ROLE_SOURCE) || + (rec_role == HAL_HEALTH_MDEP_ROLE_SOURCE && + app_role == HAL_HEALTH_MDEP_ROLE_SINK)) + return true; + + return false; +} + +static bool get_mdep_from_rec(const sdp_record_t *rec, uint8_t role, + uint16_t d_type, uint8_t *mdep) +{ + sdp_data_t *list, *feat; + + if (!mdep) + return false; + + list = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST); + if (!list || !SDP_IS_SEQ(list->dtd)) + return false; + + for (feat = list->val.dataseq; feat; feat = feat->next) { + sdp_data_t *data_type, *mdepid, *role_t; + + if (!SDP_IS_SEQ(feat->dtd)) + continue; + + mdepid = feat->val.dataseq; + if (!mdepid) + continue; + + data_type = mdepid->next; + if (!data_type) + continue; + + role_t = data_type->next; + if (!role_t) + continue; + + if (data_type->dtd != SDP_UINT16 || mdepid->dtd != SDP_UINT8 || + role_t->dtd != SDP_UINT8) + continue; + + if (data_type->val.uint16 != d_type || + !check_role(role_t->val.uint8, role)) + continue; + + *mdep = mdepid->val.uint8; + + return true; + } + + return false; +} + +static bool get_remote_mdep(sdp_list_t *recs, struct health_channel *channel) +{ + struct health_app *app; + struct mdep_cfg *mdep; + uint8_t mdep_id; + + app = queue_find(apps, match_app_by_id, + INT_TO_PTR(channel->dev->app_id)); + if (!app) + return false; + + mdep = queue_find(app->mdeps, match_mdep_by_id, + INT_TO_PTR(channel->mdep_id)); + if (!mdep) + return false; + + if (!get_mdep_from_rec(recs->data, mdep->role, mdep->data_type, + &mdep_id)) { + error("health: no matching MDEP: %u", channel->mdep_id); + return false; + } + + channel->remote_mdep = mdep_id; + return true; +} + +static bool create_mdl(struct health_channel *channel) +{ + struct health_app *app; + struct mdep_cfg *mdep; + uint8_t type; + GError *gerr = NULL; + + app = queue_find(apps, match_app_by_id, + INT_TO_PTR(channel->dev->app_id)); + if (!app) + return false; + + mdep = queue_find(app->mdeps, match_mdep_by_id, + INT_TO_PTR(channel->mdep_id)); + if (!mdep) + return false; + + if (mdep->role == HAL_HEALTH_MDEP_ROLE_SOURCE) + type = channel->type; + else + type = CHANNEL_TYPE_ANY; + + if (!mcap_create_mdl(channel->dev->mcl, channel->remote_mdep, + type, create_mdl_cb, channel, NULL, &gerr)) { + error("health: error creating mdl %s", gerr->message); + g_error_free(gerr); + return false; + } + + return true; +} + +static bool set_mcl_cb(struct mcap_mcl *mcl, gpointer user_data, GError **err) +{ + return mcap_mcl_set_cb(mcl, user_data, err, + MCAP_MDL_CB_CONNECTED, mcap_mdl_connected_cb, + MCAP_MDL_CB_CLOSED, mcap_mdl_closed_cb, + MCAP_MDL_CB_DELETED, mcap_mdl_deleted_cb, + MCAP_MDL_CB_ABORTED, mcap_mdl_aborted_cb, + MCAP_MDL_CB_REMOTE_CONN_REQ, mcap_mdl_conn_req_cb, + MCAP_MDL_CB_REMOTE_RECONN_REQ, mcap_mdl_reconn_req_cb, + MCAP_MDL_CB_INVALID); +} + +static void create_mcl_cb(struct mcap_mcl *mcl, GError *err, gpointer data) +{ + struct health_channel *channel = data; + gboolean ret; + GError *gerr = NULL; + + DBG(""); + + if (err) { + error("health: error creating MCL : %s", err->message); + goto fail; + } + + if (!channel->dev->mcl) + channel->dev->mcl = mcap_mcl_ref(mcl); + + info("health: MCL connected"); + + ret = set_mcl_cb(channel->dev->mcl, channel, &gerr); + if (!ret) { + error("health: error setting mdl callbacks: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + if (!create_mdl(channel)) + goto fail; + + return; + +fail: + destroy_channel(channel); +} + +static void search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct health_channel *channel = data; + GError *gerr = NULL; + + DBG(""); + + if (err < 0 || !recs) { + error("health: Error getting remote SDP records"); + goto fail; + } + + if (get_ccpsm(recs, &channel->dev->ccpsm) < 0) { + error("health: Can't get remote PSM for control channel"); + goto fail; + } + + if (get_dcpsm(recs, &channel->dev->dcpsm) < 0) { + error("health: Can't get remote PSM for data channel"); + goto fail; + } + + if (!get_remote_mdep(recs, channel)) { + error("health: Can't get remote MDEP data"); + goto fail; + } + + if (!mcap_create_mcl(mcap, &channel->dev->dst, channel->dev->ccpsm, + create_mcl_cb, channel, NULL, &gerr)) { + error("health: error creating mcl %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + return; + +fail: + destroy_channel(channel); +} + +static int connect_mcl(struct health_channel *channel) +{ + uuid_t uuid; + int err; + + DBG(""); + + bt_string2uuid(&uuid, HDP_UUID); + + err = bt_search_service(&adapter_addr, &channel->dev->dst, &uuid, + search_cb, channel, NULL, 0); + if (!err) + send_channel_state_notify(channel, + HAL_HEALTH_CHANNEL_CONNECTING, -1); + + return err; +} + +static struct health_app *get_app(uint16_t app_id) +{ + return queue_find(apps, match_app_by_id, INT_TO_PTR(app_id)); +} + +static struct health_channel *get_channel(struct health_app *app, + uint8_t mdep_index, + struct health_device *dev) +{ + struct health_channel *channel; + uint8_t index; + + if (!dev) + return NULL; + + index = mdep_index + 1; + channel = queue_find(dev->channels, match_channel_by_mdep_id, + INT_TO_PTR(index)); + if (channel) + return channel; + + return create_channel(app, index, dev); +} + +static void bt_health_connect_channel(const void *buf, uint16_t len) +{ + const struct hal_cmd_health_connect_channel *cmd = buf; + struct hal_rsp_health_connect_channel rsp; + struct health_device *dev = NULL; + struct health_channel *channel = NULL; + struct health_app *app; + + DBG(""); + + app = get_app(cmd->app_id); + if (!app) + goto send_rsp; + + dev = get_device(app, cmd->bdaddr); + + channel = get_channel(app, cmd->mdep_index, dev); + if (!channel) + goto send_rsp; + + if (!queue_length(dev->channels)) { + if (channel->type != CHANNEL_TYPE_RELIABLE) { + error("health: first data shannel should be reliable"); + goto fail; + } + } + + if (!dev->mcl) { + if (connect_mcl(channel) < 0) { + error("health: error retrieving HDP SDP record"); + goto fail; + } + } else { + /* data channel is already connected */ + if (channel->mdl && channel->mdl_conn) + goto fail; + + /* create mdl if it does not exists */ + if (!channel->mdl && !create_mdl(channel)) + goto fail; + + /* reconnect mdl if it exists */ + if (channel->mdl && !channel->mdl_conn) { + if (reconnect_mdl(channel) < 0) + goto fail; + } + + } + + rsp.channel_id = channel->id; + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_CONNECT_CHANNEL, + sizeof(rsp), &rsp, -1); + return; + +fail: + queue_remove(channel->dev->channels, channel); + free_health_channel(channel); + +send_rsp: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_CONNECT_CHANNEL, HAL_STATUS_FAILED); +} + +static void channel_delete_cb(GError *gerr, gpointer data) +{ + struct health_channel *channel = data; + + DBG(""); + + if (gerr) { + error("health: channel delete failed %s", gerr->message); + return; + } + + destroy_channel(channel); +} + +static void bt_health_destroy_channel(const void *buf, uint16_t len) +{ + const struct hal_cmd_health_destroy_channel *cmd = buf; + struct health_channel *channel; + GError *gerr = NULL; + + DBG(""); + + channel = search_channel_by_id(cmd->channel_id); + if (!channel) + goto fail; + + if (!mcap_delete_mdl(channel->mdl, channel_delete_cb, channel, + NULL, &gerr)) { + error("health: channel delete failed %s", gerr->message); + goto fail; + } + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_DESTROY_CHANNEL, HAL_STATUS_SUCCESS); + + return; + +fail: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HEALTH, + HAL_OP_HEALTH_DESTROY_CHANNEL, HAL_STATUS_INVALID); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_HEALTH_REG_APP */ + { bt_health_register_app, true, + sizeof(struct hal_cmd_health_reg_app) }, + /* HAL_OP_HEALTH_MDEP */ + { bt_health_mdep_cfg_data, true, + sizeof(struct hal_cmd_health_mdep) }, + /* HAL_OP_HEALTH_UNREG_APP */ + { bt_health_unregister_app, false, + sizeof(struct hal_cmd_health_unreg_app) }, + /* HAL_OP_HEALTH_CONNECT_CHANNEL */ + { bt_health_connect_channel, false, + sizeof(struct hal_cmd_health_connect_channel) }, + /* HAL_OP_HEALTH_DESTROY_CHANNEL */ + { bt_health_destroy_channel, false, + sizeof(struct hal_cmd_health_destroy_channel) }, +}; + +static void mcl_connected(struct mcap_mcl *mcl, gpointer data) +{ + GError *gerr = NULL; + bool ret; + + DBG(""); + + info("health: MCL connected"); + ret = set_mcl_cb(mcl, NULL, &gerr); + if (!ret) { + error("health: error setting mcl callbacks: %s", gerr->message); + g_error_free(gerr); + } +} + +static void mcl_reconnected(struct mcap_mcl *mcl, gpointer data) +{ + struct health_device *dev; + + DBG(""); + + info("health: MCL reconnected"); + dev = search_dev_by_mcl(mcl); + if (!dev) { + error("device data does not exists"); + return; + } +} + +static void mcl_disconnected(struct mcap_mcl *mcl, gpointer data) +{ + struct health_device *dev; + + DBG(""); + + info("health: MCL disconnected"); + dev = search_dev_by_mcl(mcl); + unref_mcl(dev); +} + +static void mcl_uncached(struct mcap_mcl *mcl, gpointer data) +{ + /* mcap library maintains cache of mcls, not required here */ +} + +bool bt_health_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + GError *err = NULL; + + DBG(""); + + bacpy(&adapter_addr, addr); + + mcap = mcap_create_instance(&adapter_addr, BT_IO_SEC_MEDIUM, 0, 0, + mcl_connected, mcl_reconnected, + mcl_disconnected, mcl_uncached, + NULL, /* CSP is not used right now */ + NULL, &err); + if (!mcap) { + error("health: MCAP instance creation failed %s", err->message); + g_error_free(err); + return false; + } + + hal_ipc = ipc; + apps = queue_new(); + + ipc_register(hal_ipc, HAL_SERVICE_ID_HEALTH, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; +} + +void bt_health_unregister(void) +{ + DBG(""); + + mcap_instance_unref(mcap); + queue_destroy(apps, free_health_app); + ipc_unregister(hal_ipc, HAL_SERVICE_ID_HEALTH); + hal_ipc = NULL; +}
diff --git a/android/health.h b/android/health.h new file mode 100644 index 0000000..0b32fd3 --- /dev/null +++ b/android/health.h
@@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_health_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_health_unregister(void);
diff --git a/android/hidhost.c b/android/hidhost.c new file mode 100644 index 0000000..fe0ea2f --- /dev/null +++ b/android/hidhost.c
@@ -0,0 +1,1599 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#include <glib.h> + +#include "btio/btio.h" +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" +#include "src/shared/mgmt.h" +#include "src/shared/util.h" +#include "src/shared/uhid.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/sdp-client.h" +#include "src/uuid-helper.h" +#include "src/log.h" +#include "profiles/input/hog-lib.h" + +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "bluetooth.h" +#include "gatt.h" +#include "hidhost.h" +#include "utils.h" + +#define L2CAP_PSM_HIDP_CTRL 0x11 +#define L2CAP_PSM_HIDP_INTR 0x13 + +/* HID message types */ +#define HID_MSG_HANDSHAKE 0x00 +#define HID_MSG_CONTROL 0x10 +#define HID_MSG_GET_REPORT 0x40 +#define HID_MSG_SET_REPORT 0x50 +#define HID_MSG_GET_PROTOCOL 0x60 +#define HID_MSG_SET_PROTOCOL 0x70 +#define HID_MSG_DATA 0xa0 + +#define HID_MSG_TYPE_MASK 0xf0 + +/* HID data types */ +#define HID_DATA_TYPE_INPUT 0x01 +#define HID_DATA_TYPE_OUTPUT 0x02 +#define HID_DATA_TYPE_FEATURE 0x03 + +/* HID protocol header parameters */ +#define HID_PROTO_BOOT 0x00 +#define HID_PROTO_REPORT 0x01 + +/* HID GET REPORT Size Field */ +#define HID_GET_REPORT_SIZE_FIELD 0x08 + +/* HID Virtual Cable Unplug */ +#define HID_VIRTUAL_CABLE_UNPLUG 0x05 + +#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb" + +static bdaddr_t adapter_addr; + +static GIOChannel *ctrl_io = NULL; +static GIOChannel *intr_io = NULL; +static GSList *devices = NULL; +static unsigned int hog_app = 0; + +static struct ipc *hal_ipc = NULL; + +struct hid_device { + bdaddr_t dst; + uint8_t state; + uint8_t subclass; + uint16_t vendor; + uint16_t product; + uint16_t version; + uint8_t country; + int rd_size; + void *rd_data; + uint8_t boot_dev; + GIOChannel *ctrl_io; + GIOChannel *intr_io; + guint ctrl_watch; + guint intr_watch; + struct bt_uhid *uhid; + uint8_t last_hid_msg; + struct bt_hog *hog; + int sec_level; +}; + +static int device_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct hid_device *dev = s; + const bdaddr_t *dst = user_data; + + return bacmp(&dev->dst, dst); +} + +static void hid_device_free(void *data) +{ + struct hid_device *dev = data; + + if (dev->ctrl_watch > 0) + g_source_remove(dev->ctrl_watch); + + if (dev->intr_watch > 0) + g_source_remove(dev->intr_watch); + + if (dev->intr_io) + g_io_channel_unref(dev->intr_io); + + if (dev->ctrl_io) + g_io_channel_unref(dev->ctrl_io); + + if (dev->uhid) + bt_uhid_unref(dev->uhid); + + if (dev->hog) + bt_hog_unref(dev->hog); + + g_free(dev->rd_data); + g_free(dev); +} + +static void hid_device_remove(struct hid_device *dev) +{ + devices = g_slist_remove(devices, dev); + hid_device_free(dev); +} + +static struct hid_device *hid_device_new(const bdaddr_t *addr) +{ + struct hid_device *dev; + + dev = g_new0(struct hid_device, 1); + bacpy(&dev->dst, addr); + dev->state = HAL_HIDHOST_STATE_DISCONNECTED; + dev->sec_level = BT_IO_SEC_LOW; + + devices = g_slist_append(devices, dev); + + return dev; +} + +static bool hex2buf(const uint8_t *hex, uint8_t *buf, int buf_size) +{ + int i, j; + char c; + uint8_t b; + + for (i = 0, j = 0; i < buf_size; i++, j++) { + c = toupper(hex[j]); + + if (c >= '0' && c <= '9') + b = c - '0'; + else if (c >= 'A' && c <= 'F') + b = 10 + c - 'A'; + else + return false; + + j++; + + c = toupper(hex[j]); + + if (c >= '0' && c <= '9') + b = b * 16 + c - '0'; + else if (c >= 'A' && c <= 'F') + b = b * 16 + 10 + c - 'A'; + else + return false; + + buf[i] = b; + } + + return true; +} + +static void handle_uhid_output(struct uhid_event *event, void *user_data) +{ + struct uhid_output_req *output = &event->u.output; + struct hid_device *dev = user_data; + int fd, req_size; + uint8_t *req; + + if (!dev->ctrl_io) + return; + + req_size = 1 + output->size; + req = malloc0(req_size); + if (!req) + return; + + req[0] = HID_MSG_SET_REPORT | output->rtype; + memcpy(req + 1, output->data, req_size - 1); + + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, req, req_size) < 0) + error("hidhost: error writing set_report: %s (%d)", + strerror(errno), errno); + + free(req); +} + +static gboolean intr_io_watch_cb(GIOChannel *chan, gpointer data) +{ + struct hid_device *dev = data; + uint8_t buf[UHID_DATA_MAX]; + struct uhid_event ev; + int fd, bread, err; + + /* Wait uHID if not ready */ + if (!dev->uhid) + return TRUE; + + fd = g_io_channel_unix_get_fd(chan); + bread = read(fd, buf, sizeof(buf)); + if (bread < 0) { + error("hidhost: read from interrupt failed: %s(%d)", + strerror(errno), -errno); + return TRUE; + } + + /* Discard non-data packets */ + if (bread == 0 || buf[0] != (HID_MSG_DATA | HID_DATA_TYPE_INPUT)) + return TRUE; + + /* send data to uHID device skipping HIDP header byte */ + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + ev.u.input.size = bread - 1; + memcpy(ev.u.input.data, &buf[1], ev.u.input.size); + + err = bt_uhid_send(dev->uhid, &ev); + if (err < 0) + DBG("bt_uhid_send: %s (%d)", strerror(-err), -err); + + return TRUE; +} + +static void bt_hid_notify_state(struct hid_device *dev, uint8_t state) +{ + struct hal_ev_hidhost_conn_state ev; + char address[18]; + + if (dev->state == state) + return; + + dev->state = state; + + ba2str(&dev->dst, address); + DBG("device %s state %u", address, state); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.state = state; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_CONN_STATE, sizeof(ev), &ev); +} + +static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct hid_device *dev = data; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + goto error; + + if (cond & G_IO_IN) + return intr_io_watch_cb(chan, data); + +error: + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + + /* + * 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)) && dev->ctrl_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + /* Close control channel */ + if (dev->ctrl_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); + + hid_device_remove(dev); + + return FALSE; +} + +static void bt_hid_notify_proto_mode(struct hid_device *dev, uint8_t *buf, + int len) +{ + struct hal_ev_hidhost_proto_mode ev; + char address[18]; + + ba2str(&dev->dst, address); + DBG("device %s", address); + + memset(&ev, 0, sizeof(ev)); + bdaddr2android(&dev->dst, ev.bdaddr); + + if (buf[0] == HID_MSG_DATA) { + ev.status = HAL_HIDHOST_STATUS_OK; + if (buf[1] == HID_PROTO_REPORT) + ev.mode = HAL_HIDHOST_REPORT_PROTOCOL; + else if (buf[1] == HID_PROTO_BOOT) + ev.mode = HAL_HIDHOST_BOOT_PROTOCOL; + else + ev.mode = HAL_HIDHOST_UNSUPPORTED_PROTOCOL; + + } else { + ev.status = buf[0]; + ev.mode = HAL_HIDHOST_UNSUPPORTED_PROTOCOL; + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_PROTO_MODE, sizeof(ev), &ev); +} + +static void bt_hid_notify_get_report(struct hid_device *dev, uint8_t *buf, + int len) +{ + struct hal_ev_hidhost_get_report *ev; + int ev_len; + char address[18]; + + ba2str(&dev->dst, address); + DBG("device %s", address); + + ev_len = sizeof(*ev); + + if (!((buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_INPUT)) || + (buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_OUTPUT)) || + (buf[0] == (HID_MSG_DATA | HID_DATA_TYPE_FEATURE)))) { + ev = g_malloc0(ev_len); + ev->status = buf[0]; + bdaddr2android(&dev->dst, ev->bdaddr); + goto send; + } + + /* + * Report porotocol mode reply contains id after hdr, in boot + * protocol mode id doesn't exist + */ + ev_len += (dev->boot_dev) ? (len - 1) : (len - 2); + ev = g_malloc0(ev_len); + ev->status = HAL_HIDHOST_STATUS_OK; + bdaddr2android(&dev->dst, ev->bdaddr); + + /* + * Report porotocol mode reply contains id after hdr, in boot + * protocol mode id doesn't exist + */ + if (dev->boot_dev) { + ev->len = len - 1; + memcpy(ev->data, buf + 1, ev->len); + } else { + ev->len = len - 2; + memcpy(ev->data, buf + 2, ev->len); + } + +send: + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_GET_REPORT, ev_len, ev); + g_free(ev); +} + +static void bt_hid_notify_handshake(struct hid_device *dev, uint8_t *buf, + int len) +{ + struct hal_ev_hidhost_handshake ev; + + bdaddr2android(&dev->dst, ev.bdaddr); + + /* crop result code to handshake status range from HAL */ + ev.status = buf[0]; + if (ev.status > HAL_HIDHOST_HS_ERROR) + ev.status = HAL_HIDHOST_HS_ERROR; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_HANDSHAKE, sizeof(ev), &ev); +} + +static void bt_hid_notify_virtual_unplug(struct hid_device *dev, + uint8_t *buf, int len) +{ + struct hal_ev_hidhost_virtual_unplug ev; + char address[18]; + + ba2str(&dev->dst, address); + DBG("device %s", address); + bdaddr2android(&dev->dst, ev.bdaddr); + + ev.status = HAL_HIDHOST_GENERAL_ERROR; + + /* Wait either channels to HUP */ + if (dev->intr_io && dev->ctrl_io) { + g_io_channel_shutdown(dev->intr_io, TRUE, NULL); + g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTING); + ev.status = HAL_HIDHOST_STATUS_OK; + } + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_EV_HIDHOST_VIRTUAL_UNPLUG, sizeof(ev), &ev); +} + +static gboolean ctrl_io_watch_cb(GIOChannel *chan, gpointer data) +{ + struct hid_device *dev = data; + int fd, bread; + uint8_t buf[UHID_DATA_MAX]; + + DBG(""); + + fd = g_io_channel_unix_get_fd(chan); + bread = read(fd, buf, sizeof(buf)); + if (bread < 0) { + error("hidhost: read from control failed: %s(%d)", + strerror(errno), -errno); + return TRUE; + } + + switch (dev->last_hid_msg) { + case HID_MSG_GET_PROTOCOL: + case HID_MSG_SET_PROTOCOL: + bt_hid_notify_proto_mode(dev, buf, bread); + break; + case HID_MSG_GET_REPORT: + bt_hid_notify_get_report(dev, buf, bread); + break; + } + + switch (buf[0] & HID_MSG_TYPE_MASK) { + case HID_MSG_HANDSHAKE: + bt_hid_notify_handshake(dev, buf, bread); + break; + case HID_MSG_CONTROL: + if ((buf[0] & ~HID_MSG_TYPE_MASK) == HID_VIRTUAL_CABLE_UNPLUG) + bt_hid_notify_virtual_unplug(dev, buf, bread); + break; + default: + break; + } + + /* reset msg type request */ + dev->last_hid_msg = 0; + + return TRUE; +} + +static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct hid_device *dev = data; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + goto error; + + if (cond & G_IO_IN) + return ctrl_io_watch_cb(chan, data); + +error: + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + + /* + * 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)) && dev->intr_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + if (dev->intr_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(dev->intr_io, TRUE, NULL); + + hid_device_remove(dev); + + return FALSE; +} + +static void bt_hid_set_info(struct hid_device *dev) +{ + struct hal_ev_hidhost_info ev; + + DBG(""); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.attr = 0; /* TODO: Check what is this field */ + ev.subclass = dev->subclass; + ev.app_id = 0; /* TODO: Check what is this field */ + ev.vendor = dev->vendor; + ev.product = dev->product; + ev.version = dev->version; + ev.country = dev->country; + ev.descr_len = dev->rd_size; + memset(ev.descr, 0, sizeof(ev.descr)); + memcpy(ev.descr, dev->rd_data, ev.descr_len); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_EV_HIDHOST_INFO, + sizeof(ev), &ev); +} + +static int uhid_create(struct hid_device *dev) +{ + struct uhid_event ev; + int err; + + dev->uhid = bt_uhid_new_default(); + if (!dev->uhid) { + err = -errno; + error("hidhost: Failed to create bt_uhid instance"); + return err; + } + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char *) ev.u.create.name, "bluez-input-device"); + ev.u.create.bus = BUS_BLUETOOTH; + ev.u.create.vendor = dev->vendor; + ev.u.create.product = dev->product; + ev.u.create.version = dev->version; + ev.u.create.country = dev->country; + ev.u.create.rd_size = dev->rd_size; + ev.u.create.rd_data = dev->rd_data; + + err = bt_uhid_send(dev->uhid, &ev); + if (err < 0) { + error("hidhost: Failed to create uHID device: %s", + strerror(-err)); + bt_uhid_unref(dev->uhid); + dev->uhid = NULL; + return err; + } + + bt_uhid_register(dev->uhid, UHID_OUTPUT, handle_uhid_output, dev); + bt_hid_set_info(dev); + + return 0; +} + +static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct hid_device *dev = user_data; + uint8_t state; + + DBG(""); + + if (conn_err) { + error("hidhost: Failed to connect interrupt channel (%s)", + conn_err->message); + state = HAL_HIDHOST_STATE_FAILED; + goto failed; + } + + if (uhid_create(dev) < 0) { + state = HAL_HIDHOST_STATE_NO_HID; + goto failed; + } + + dev->intr_watch = g_io_add_watch(dev->intr_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + intr_watch_cb, dev); + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); + + return; + +failed: + bt_hid_notify_state(dev, state); + hid_device_remove(dev); +} + +static void control_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct hid_device *dev = user_data; + GError *err = NULL; + + DBG(""); + + if (conn_err) { + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + error("hidhost: Failed to connect control channel (%s)", + conn_err->message); + goto failed; + } + + /* Connect to the HID interrupt channel */ + dev->intr_io = bt_io_connect(interrupt_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, dev->sec_level, + BT_IO_OPT_INVALID); + if (!dev->intr_io) { + error("hidhost: Failed to connect interrupt channel (%s)", + err->message); + g_error_free(err); + goto failed; + } + + dev->ctrl_watch = g_io_add_watch(dev->ctrl_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + ctrl_watch_cb, dev); + + return; + +failed: + hid_device_remove(dev); +} + +static void hid_sdp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct hid_device *dev = data; + sdp_list_t *list; + GError *gerr = NULL; + + DBG(""); + + if (err < 0) { + error("hidhost: Unable to get SDP record: %s", strerror(-err)); + goto fail; + } + + if (!recs || !recs->data) { + error("hidhost: No SDP records found"); + goto fail; + } + + for (list = recs; list != NULL; list = list->next) { + sdp_record_t *rec = list->data; + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE); + if (data) + dev->country = data->val.uint8; + + data = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS); + if (data) { + dev->subclass = data->val.uint8; + + /* Encryption is mandatory for keyboards */ + if (dev->subclass & 0x40) + dev->sec_level = BT_IO_SEC_MEDIUM; + } + + data = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE); + if (data) + dev->boot_dev = data->val.uint8; + + data = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); + if (data) { + if (!SDP_IS_SEQ(data->dtd)) + goto fail; + + /* First HIDDescriptor */ + data = data->val.dataseq; + if (!SDP_IS_SEQ(data->dtd)) + goto fail; + + /* ClassDescriptorType */ + data = data->val.dataseq; + if (data->dtd != SDP_UINT8) + goto fail; + + /* ClassDescriptorData */ + data = data->next; + if (!data || !SDP_IS_TEXT_STR(data->dtd)) + goto fail; + + dev->rd_size = data->unitSize; + dev->rd_data = g_memdup(data->val.str, data->unitSize); + } + } + + if (dev->ctrl_io) { + /* Raise the security level for this device if needed. */ + if ((dev->sec_level > BT_IO_SEC_LOW) && + !bt_io_set(dev->ctrl_io, &gerr, + BT_IO_OPT_SEC_LEVEL, dev->sec_level, + BT_IO_OPT_INVALID)) { + error("hidhost: Cannot raise security level: %s", + gerr->message); + g_error_free(gerr); + + goto fail; + } + + if (uhid_create(dev) < 0) + goto fail; + return; + } + + dev->ctrl_io = bt_io_connect(control_connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, dev->sec_level, + BT_IO_OPT_INVALID); + if (gerr) { + error("hidhost: Failed to connect control channel (%s)", + gerr->message); + g_error_free(gerr); + goto fail; + } + + return; + +fail: + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + hid_device_remove(dev); +} + +static void hid_sdp_did_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct hid_device *dev = data; + sdp_list_t *list; + uuid_t uuid; + + DBG(""); + + if (err < 0) { + error("hidhost: Unable to get Device ID SDP record: %s", + strerror(-err)); + goto fail; + } + + if (!recs || !recs->data) { + error("hidhost: No Device ID SDP records found"); + goto fail; + } + + for (list = recs; list; list = list->next) { + sdp_record_t *rec = list->data; + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); + if (data) + dev->vendor = data->val.uint16; + + data = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); + if (data) + dev->product = data->val.uint16; + + data = sdp_data_get(rec, SDP_ATTR_VERSION); + if (data) + dev->version = data->val.uint16; + } + + sdp_uuid16_create(&uuid, HID_SVCLASS_ID); + if (bt_search_service(&adapter_addr, &dev->dst, &uuid, + hid_sdp_search_cb, dev, NULL, 0) < 0) { + error("hidhost: Failed to search SDP details"); + goto fail; + } + + return; + +fail: + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + hid_device_remove(dev); +} + +static void hog_conn_cb(const bdaddr_t *addr, int err, void *attrib) +{ + GSList *l; + struct hid_device *dev; + + l = g_slist_find_custom(devices, addr, device_cmp); + dev = l ? l->data : NULL; + + if (err < 0) { + if (!dev) + return; + if (dev->hog) { + bt_hid_notify_state(dev, + HAL_HIDHOST_STATE_DISCONNECTED); + bt_hog_detach(dev->hog); + return; + } + goto fail; + } + + if (!dev) + dev = hid_device_new(addr); + + if (!dev->hog) { + /* TODO: Get device details and primary */ + dev->hog = bt_hog_new_default("bluez-input-device", dev->vendor, + dev->product, dev->version, NULL); + if (!dev->hog) { + error("HoG: unable to create session"); + goto fail; + } + } + + if (!bt_hog_attach(dev->hog, attrib)) { + error("HoG: unable to attach"); + goto fail; + } + + if (!bt_gatt_set_security(addr, BT_IO_SEC_MEDIUM)) { + error("Failed to set security level"); + goto fail; + } + + DBG(""); + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); + + if (!bt_gatt_add_autoconnect(hog_app, &dev->dst)) + error("hidhost: Could not add to autoconnect list"); + + return; + +fail: + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + hid_device_remove(dev); +} + +static bool hog_connect(struct hid_device *dev) +{ + DBG(""); + + if (hog_app) + return bt_gatt_connect_app(hog_app, &dev->dst); + + hog_app = bt_gatt_register_app(HOG_UUID, GATT_CLIENT, hog_conn_cb); + if (!hog_app) { + error("hidhost: bt_gatt_register_app failed"); + return false; + } + + return bt_gatt_connect_app(hog_app, &dev->dst); +} + +static void bt_hid_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_connect *cmd = buf; + struct hid_device *dev; + uint8_t status; + char addr[18]; + bdaddr_t dst; + GSList *l; + uuid_t uuid; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) + dev = l->data; + else + dev = hid_device_new(&dst); + + if (dev->state != HAL_HIDHOST_STATE_DISCONNECTED) + goto done; + + ba2str(&dev->dst, addr); + DBG("connecting to %s", addr); + + if (bt_device_last_seen_bearer(&dev->dst) != BDADDR_BREDR) { + if (!hog_connect(dev)) { + status = HAL_STATUS_FAILED; + hid_device_remove(dev); + goto failed; + } + goto done; + } + + sdp_uuid16_create(&uuid, PNP_INFO_SVCLASS_ID); + if (bt_search_service(&adapter_addr, &dev->dst, &uuid, + hid_sdp_did_search_cb, dev, NULL, 0) < 0) { + error("hidhost: Failed to search DeviceID SDP details"); + hid_device_remove(dev); + status = HAL_STATUS_FAILED; + goto failed; + } + +done: + if (dev->state == HAL_HIDHOST_STATE_DISCONNECTED) + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTING); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_CONNECT, + status); +} + +static bool hog_disconnect(struct hid_device *dev) +{ + DBG(""); + + if (dev->state == HAL_HIDHOST_STATE_DISCONNECTED) + return false; + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTING); + + if (!bt_gatt_disconnect_app(hog_app, &dev->dst)) { + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTED); + hid_device_remove(dev); + } + + return true; +} + +static void bt_hid_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_disconnect *cmd = buf; + struct hid_device *dev; + uint8_t status; + GSList *l; + bdaddr_t dst; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + if (bt_is_device_le(&dst)) { + if (!hog_disconnect(dev)) { + status = HAL_STATUS_FAILED; + goto failed; + } + goto done; + } + + /* Wait either channels to HUP */ + if (dev->intr_io) + g_io_channel_shutdown(dev->intr_io, TRUE, NULL); + + if (dev->ctrl_io) + g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTING); + + +done: + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_DISCONNECT, + status); +} + +static bool bt_hid_write_virtual_unplug(GIOChannel *chan) +{ + uint8_t hdr = HID_MSG_CONTROL | HID_VIRTUAL_CABLE_UNPLUG; + int fd = g_io_channel_unix_get_fd(chan); + + if (write(fd, &hdr, sizeof(hdr)) == sizeof(hdr)) + return true; + + error("hidhost: Error writing virtual unplug command: %s (%d)", + strerror(errno), errno); + return false; +} + +static void bt_hid_virtual_unplug(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_virtual_unplug *cmd = buf; + struct hid_device *dev; + GSList *l; + uint8_t status; + bdaddr_t dst; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + + if (!(dev->ctrl_io)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + if (!bt_hid_write_virtual_unplug(dev->ctrl_io)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + /* Wait either channels to HUP */ + if (dev->intr_io) + g_io_channel_shutdown(dev->intr_io, TRUE, NULL); + + if (dev->ctrl_io) + g_io_channel_shutdown(dev->ctrl_io, TRUE, NULL); + + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_DISCONNECTING); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_VIRTUAL_UNPLUG, status); +} + +static void bt_hid_info(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_set_info *cmd = buf; + + if (len != sizeof(*cmd) + cmd->descr_len) { + error("Invalid hid set info size (%u bytes), terminating", len); + raise(SIGTERM); + return; + } + + /* + * Data from hal_cmd_hidhost_set_info is usefull only when we create + * UHID device. Once device is created all the transactions will be + * done through the fd. There is no way to use this information + * once device is created with HID internals. + */ + DBG("Not supported"); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_SET_INFO, + HAL_STATUS_UNSUPPORTED); +} + +static void bt_hid_get_protocol(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_get_protocol *cmd = buf; + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t hdr; + uint8_t status; + + DBG(""); + + switch (cmd->mode) { + case HAL_HIDHOST_REPORT_PROTOCOL: + case HAL_HIDHOST_BOOT_PROTOCOL: + break; + default: + status = HAL_STATUS_INVALID; + goto failed; + } + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + + hdr = HID_MSG_GET_PROTOCOL | cmd->mode; + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, &hdr, sizeof(hdr)) < 0) { + error("hidhost: Error writing device_get_protocol: %s (%d)", + strerror(errno), errno); + status = HAL_STATUS_FAILED; + goto failed; + } + + dev->last_hid_msg = HID_MSG_GET_PROTOCOL; + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_GET_PROTOCOL, status); +} + +static void bt_hid_set_protocol(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_set_protocol *cmd = buf; + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t hdr; + uint8_t status; + + DBG(""); + + switch (cmd->mode) { + case HAL_HIDHOST_REPORT_PROTOCOL: + case HAL_HIDHOST_BOOT_PROTOCOL: + break; + default: + status = HAL_STATUS_INVALID; + goto failed; + } + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + + hdr = HID_MSG_SET_PROTOCOL | cmd->mode; + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, &hdr, sizeof(hdr)) < 0) { + error("hidhost: error writing device_set_protocol: %s (%d)", + strerror(errno), errno); + status = HAL_STATUS_FAILED; + goto failed; + } + + dev->last_hid_msg = HID_MSG_SET_PROTOCOL; + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_PROTOCOL, status); +} + +static void bt_hid_get_report(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_get_report *cmd = buf; + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t *req; + uint8_t req_size; + uint8_t status; + + DBG(""); + + switch (cmd->type) { + case HAL_HIDHOST_INPUT_REPORT: + case HAL_HIDHOST_OUTPUT_REPORT: + case HAL_HIDHOST_FEATURE_REPORT: + break; + default: + status = HAL_STATUS_INVALID; + goto failed; + } + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + req_size = (cmd->buf_size > 0) ? 4 : 2; + req = g_try_malloc0(req_size); + if (!req) { + status = HAL_STATUS_NOMEM; + goto failed; + } + + req[0] = HID_MSG_GET_REPORT | cmd->type; + req[1] = cmd->id; + + if (cmd->buf_size > 0) { + req[0] = req[0] | HID_GET_REPORT_SIZE_FIELD; + put_le16(cmd->buf_size, &req[2]); + } + + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, req, req_size) < 0) { + error("hidhost: error writing hid_get_report: %s (%d)", + strerror(errno), errno); + g_free(req); + status = HAL_STATUS_FAILED; + goto failed; + } + + dev->last_hid_msg = HID_MSG_GET_REPORT; + g_free(req); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_GET_REPORT, + status); +} + +static void bt_hid_set_report(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_set_report *cmd = buf; + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t *req = NULL; + uint8_t req_size; + uint8_t status; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid hid set report size (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + switch (cmd->type) { + case HAL_HIDHOST_INPUT_REPORT: + case HAL_HIDHOST_OUTPUT_REPORT: + case HAL_HIDHOST_FEATURE_REPORT: + break; + default: + status = HAL_STATUS_INVALID; + goto failed; + } + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + + if (!dev->ctrl_io && !dev->hog) { + status = HAL_STATUS_FAILED; + goto failed; + } + + req_size = 1 + (cmd->len / 2); + req = g_try_malloc0(req_size); + if (!req) { + status = HAL_STATUS_NOMEM; + goto failed; + } + + req[0] = HID_MSG_SET_REPORT | cmd->type; + /* + * Report data coming to HAL is in ascii format, HAL sends + * data in hex to daemon, so convert to binary. + */ + if (!hex2buf(cmd->data, req + 1, req_size - 1)) { + status = HAL_STATUS_INVALID; + goto failed; + } + + if (dev->hog) { + if (bt_hog_send_report(dev->hog, req + 1, req_size - 1, + cmd->type) < 0) { + status = HAL_STATUS_FAILED; + goto failed; + } + + goto done; + } + + fd = g_io_channel_unix_get_fd(dev->ctrl_io); + + if (write(fd, req, req_size) < 0) { + error("hidhost: error writing hid_set_report: %s (%d)", + strerror(errno), errno); + status = HAL_STATUS_FAILED; + goto failed; + } + + dev->last_hid_msg = HID_MSG_SET_REPORT; + +done: + status = HAL_STATUS_SUCCESS; + +failed: + g_free(req); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_SET_REPORT, + status); +} + +static void bt_hid_send_data(const void *buf, uint16_t len) +{ + const struct hal_cmd_hidhost_send_data *cmd = buf; + struct hid_device *dev; + GSList *l; + bdaddr_t dst; + int fd; + uint8_t *req = NULL; + uint8_t req_size; + uint8_t status; + + DBG(""); + + if (len != sizeof(*cmd) + cmd->len) { + error("Invalid hid send data size (%u bytes), terminating", + len); + raise(SIGTERM); + return; + } + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + + if (!(dev->intr_io)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + req_size = 1 + (cmd->len / 2); + req = g_try_malloc0(req_size); + if (!req) { + status = HAL_STATUS_NOMEM; + goto failed; + } + + req[0] = HID_MSG_DATA | HID_DATA_TYPE_OUTPUT; + /* + * Report data coming to HAL is in ascii format, HAL sends + * data in hex to daemon, so convert to binary. + */ + if (!hex2buf(cmd->data, req + 1, req_size - 1)) { + status = HAL_STATUS_INVALID; + goto failed; + } + + fd = g_io_channel_unix_get_fd(dev->intr_io); + + if (write(fd, req, req_size) < 0) { + error("hidhost: error writing data to HID device: %s (%d)", + strerror(errno), errno); + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + g_free(req); + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_HIDHOST, HAL_OP_HIDHOST_SEND_DATA, + status); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_HIDHOST_CONNECT */ + { bt_hid_connect, false, sizeof(struct hal_cmd_hidhost_connect) }, + /* HAL_OP_HIDHOST_DISCONNECT */ + { bt_hid_disconnect, false, sizeof(struct hal_cmd_hidhost_disconnect) }, + /* HAL_OP_HIDHOST_VIRTUAL_UNPLUG */ + { bt_hid_virtual_unplug, false, + sizeof(struct hal_cmd_hidhost_virtual_unplug) }, + /* HAL_OP_HIDHOST_SET_INFO */ + { bt_hid_info, true, sizeof(struct hal_cmd_hidhost_set_info) }, + /* HAL_OP_HIDHOST_GET_PROTOCOL */ + { bt_hid_get_protocol, false, + sizeof(struct hal_cmd_hidhost_get_protocol) }, + /* HAL_OP_HIDHOST_SET_PROTOCOL */ + { bt_hid_set_protocol, false, + sizeof(struct hal_cmd_hidhost_get_protocol) }, + /* HAL_OP_HIDHOST_GET_REPORT */ + { bt_hid_get_report, false, sizeof(struct hal_cmd_hidhost_get_report) }, + /* HAL_OP_HIDHOST_SET_REPORT */ + { bt_hid_set_report, true, sizeof(struct hal_cmd_hidhost_set_report) }, + /* HAL_OP_HIDHOST_SEND_DATA */ + { bt_hid_send_data, true, sizeof(struct hal_cmd_hidhost_send_data) }, +}; + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct hid_device *dev; + bdaddr_t dst; + char address[18]; + uint16_t psm; + GError *gerr = NULL; + GSList *l; + uuid_t uuid; + + if (err) { + error("hidhost: Connect failed (%s)", err->message); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_PSM, &psm, + BT_IO_OPT_INVALID); + if (gerr) { + error("hidhost: Failed to read remote address (%s)", + gerr->message); + g_io_channel_shutdown(chan, TRUE, NULL); + g_error_free(gerr); + return; + } + + ba2str(&dst, address); + DBG("Incoming connection from %s on PSM %d", address, psm); + + if (!bt_device_is_bonded(&dst)) { + warn("hidhost: Rejecting connection from unknown device %s", + address); + if (psm == L2CAP_PSM_HIDP_CTRL) + bt_hid_write_virtual_unplug(chan); + + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + switch (psm) { + case L2CAP_PSM_HIDP_CTRL: + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) + return; + + dev = hid_device_new(&dst); + dev->ctrl_io = g_io_channel_ref(chan); + + sdp_uuid16_create(&uuid, PNP_INFO_SVCLASS_ID); + if (bt_search_service(&adapter_addr, &dev->dst, &uuid, + hid_sdp_did_search_cb, dev, NULL, 0) < 0) { + error("hidhost: Failed to search DID SDP details"); + hid_device_remove(dev); + return; + } + + dev->ctrl_watch = g_io_add_watch(dev->ctrl_io, + G_IO_HUP | G_IO_ERR | G_IO_NVAL, + ctrl_watch_cb, dev); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTING); + break; + + case L2CAP_PSM_HIDP_INTR: + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) + return; + + dev = l->data; + dev->intr_io = g_io_channel_ref(chan); + dev->intr_watch = g_io_add_watch(dev->intr_io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + intr_watch_cb, dev); + bt_hid_notify_state(dev, HAL_HIDHOST_STATE_CONNECTED); + break; + } +} + +static void hid_unpaired_cb(const bdaddr_t *addr) +{ + GSList *l; + struct hid_device *dev; + char address[18]; + + l = g_slist_find_custom(devices, addr, device_cmp); + if (!l) + return; + + dev = l->data; + + ba2str(addr, address); + DBG("Unpaired device %s", address); + + if (hog_app) + bt_gatt_remove_autoconnect(hog_app, addr); + + hid_device_remove(dev); +} + +bool bt_hid_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + GError *err = NULL; + + DBG(""); + + if (!bt_unpaired_register(hid_unpaired_cb)) { + error("hidhost: Could not register unpaired callback"); + return false; + } + + bacpy(&adapter_addr, addr); + + ctrl_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!ctrl_io) { + error("hidhost: Failed to listen on control channel: %s", + err->message); + g_error_free(err); + return false; + } + + intr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!intr_io) { + error("hidhost: Failed to listen on interrupt channel: %s", + err->message); + g_error_free(err); + + g_io_channel_shutdown(ctrl_io, TRUE, NULL); + g_io_channel_unref(ctrl_io); + ctrl_io = NULL; + + return false; + } + + hal_ipc = ipc; + + ipc_register(hal_ipc, HAL_SERVICE_ID_HIDHOST, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; +} + +void bt_hid_unregister(void) +{ + DBG(""); + + if (hog_app > 0) + bt_gatt_unregister_app(hog_app); + + g_slist_free_full(devices, hid_device_free); + devices = NULL; + + if (ctrl_io) { + g_io_channel_shutdown(ctrl_io, TRUE, NULL); + g_io_channel_unref(ctrl_io); + ctrl_io = NULL; + } + + if (intr_io) { + g_io_channel_shutdown(intr_io, TRUE, NULL); + g_io_channel_unref(intr_io); + intr_io = NULL; + } + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_HIDHOST); + hal_ipc = NULL; + + bt_unpaired_unregister(hid_unpaired_cb); +}
diff --git a/android/hidhost.h b/android/hidhost.h new file mode 100644 index 0000000..e6b87ed --- /dev/null +++ b/android/hidhost.h
@@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_hid_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_hid_unregister(void);
diff --git a/android/init.bluetooth.rc b/android/init.bluetooth.rc new file mode 100644 index 0000000..2d43f73 --- /dev/null +++ b/android/init.bluetooth.rc
@@ -0,0 +1,38 @@ +# required permissions +on boot + chown bluetooth bluetooth /data/misc/bluetooth + chown bluetooth bluetooth /dev/uhid + chown system bluetooth /dev/uinput + +# services +on property:bluetooth.start=daemon + setprop bluetooth.start none + start bluetoothd + +on property:bluetooth.stop=daemon + setprop bluetooth.stop none + stop bluetoothd + +on property:bluetooth.start=snoop + setprop bluetooth.start none + start bluetoothd-snoop + +on property:bluetooth.stop=snoop + setprop bluetooth.stop none + stop bluetoothd-snoop + +service bluetoothd /system/bin/bluetoothd + class main + # init does not yet support setting capabilities so run as root, + # bluetoothd drop uid to bluetooth with the right linux capabilities + group bluetooth + disabled + oneshot + +service bluetoothd-snoop /system/bin/bluetoothd-snoop + class main + # init does not yet support setting capabilities so run as root, + # bluetoothd-snoop drops unneeded linux capabilities + group nobody + disabled + oneshot
diff --git a/android/ipc-common.h b/android/ipc-common.h new file mode 100644 index 0000000..27736e4 --- /dev/null +++ b/android/ipc-common.h
@@ -0,0 +1,38 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 IPC_MTU 1024 + +#define IPC_STATUS_SUCCESS 0x00 + +struct ipc_hdr { + uint8_t service_id; + uint8_t opcode; + uint16_t len; + uint8_t payload[0]; +} __attribute__((packed)); + +#define IPC_OP_STATUS 0x00 +struct ipc_status { + uint8_t code; +} __attribute__((packed));
diff --git a/android/ipc-tester.c b/android/ipc-tester.c new file mode 100644 index 0000000..1aa17d2 --- /dev/null +++ b/android/ipc-tester.c
@@ -0,0 +1,1512 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <unistd.h> +#include <errno.h> +#include <poll.h> + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <libgen.h> +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" + +#include "src/shared/tester.h" +#include "src/shared/mgmt.h" +#include "emulator/hciemu.h" + +#include "hal-msg.h" +#include "ipc-common.h" + +#include <cutils/properties.h> + +#define WAIT_FOR_SIGNAL_TIME 2 /* in seconds */ +#define EMULATOR_SIGNAL "emulator_started" + +struct test_data { + struct mgmt *mgmt; + uint16_t mgmt_index; + struct hciemu *hciemu; + enum hciemu_type hciemu_type; + pid_t bluetoothd_pid; + bool setup_done; +}; + +struct ipc_data { + void *buffer; + size_t len; +}; + +struct generic_data { + struct ipc_data ipc_data; + + unsigned int num_services; + int init_services[]; +}; + +struct regmod_msg { + struct ipc_hdr header; + struct hal_cmd_register_module cmd; +} __attribute__((packed)); + +#define CONNECT_TIMEOUT (5 * 1000) +#define SERVICE_NAME "bluetoothd" + +static char exec_dir[PATH_MAX]; + +static int cmd_sk = -1; +static int notif_sk = -1; + +static void read_info_callback(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct test_data *data = tester_get_data(); + const struct mgmt_rp_read_info *rp = param; + char addr[18]; + uint16_t manufacturer; + uint32_t supported_settings, current_settings; + + tester_print("Read Info callback"); + tester_print(" Status: 0x%02x", status); + + if (status || !param) { + tester_pre_setup_failed(); + return; + } + + ba2str(&rp->bdaddr, addr); + manufacturer = btohs(rp->manufacturer); + supported_settings = btohl(rp->supported_settings); + current_settings = btohl(rp->current_settings); + + tester_print(" Address: %s", addr); + tester_print(" Version: 0x%02x", rp->version); + tester_print(" Manufacturer: 0x%04x", manufacturer); + tester_print(" Supported settings: 0x%08x", supported_settings); + tester_print(" Current settings: 0x%08x", current_settings); + tester_print(" Class: 0x%02x%02x%02x", + rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]); + tester_print(" Name: %s", rp->name); + tester_print(" Short name: %s", rp->short_name); + + if (strcmp(hciemu_get_address(data->hciemu), addr)) { + tester_pre_setup_failed(); + return; + } + + tester_pre_setup_complete(); +} + +static void index_added_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + struct test_data *data = tester_get_data(); + + tester_print("Index Added callback"); + tester_print(" Index: 0x%04x", index); + + data->mgmt_index = index; + + mgmt_send(data->mgmt, MGMT_OP_READ_INFO, data->mgmt_index, 0, NULL, + read_info_callback, NULL, NULL); +} + +static void index_removed_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + struct test_data *data = tester_get_data(); + + tester_print("Index Removed callback"); + tester_print(" Index: 0x%04x", index); + + if (index != data->mgmt_index) + return; + + mgmt_unregister_index(data->mgmt, data->mgmt_index); + + mgmt_unref(data->mgmt); + data->mgmt = NULL; + + tester_post_teardown_complete(); +} + +static void read_index_list_callback(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct test_data *data = tester_get_data(); + + tester_print("Read Index List callback"); + tester_print(" Status: 0x%02x", status); + + if (status || !param) { + tester_pre_setup_failed(); + return; + } + + mgmt_register(data->mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, + index_added_callback, NULL, NULL); + + mgmt_register(data->mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, + index_removed_callback, NULL, NULL); + + data->hciemu = hciemu_new(data->hciemu_type); + if (!data->hciemu) { + tester_warn("Failed to setup HCI emulation"); + tester_pre_setup_failed(); + return; + } + + tester_print("New hciemu instance created"); +} + +static void test_pre_setup(const void *data) +{ + struct test_data *test_data = tester_get_data(); + + if (!tester_use_debug()) + fclose(stderr); + + test_data->mgmt = mgmt_new_default(); + if (!test_data->mgmt) { + tester_warn("Failed to setup management interface"); + tester_pre_setup_failed(); + return; + } + + mgmt_send(test_data->mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, + NULL, read_index_list_callback, NULL, NULL); +} + +static void test_post_teardown(const void *data) +{ + struct test_data *test_data = tester_get_data(); + + if (test_data->hciemu) { + hciemu_unref(test_data->hciemu); + test_data->hciemu = NULL; + } +} + +static void bluetoothd_start(int hci_index) +{ + char prg_name[PATH_MAX]; + char index[8]; + char *prg_argv[4]; + + snprintf(prg_name, sizeof(prg_name), "%s/%s", exec_dir, "bluetoothd"); + snprintf(index, sizeof(index), "%d", hci_index); + + prg_argv[0] = prg_name; + prg_argv[1] = "-i"; + prg_argv[2] = index; + prg_argv[3] = NULL; + + if (!tester_use_debug()) + fclose(stderr); + + execve(prg_argv[0], prg_argv, NULL); +} + +static void emulator(int pipe, int hci_index) +{ + static const char SYSTEM_SOCKET_PATH[] = "\0android_system"; + char buf[1024]; + struct sockaddr_un addr; + struct timeval tv; + int fd; + ssize_t len; + + fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + goto failed; + + tv.tv_sec = WAIT_FOR_SIGNAL_TIME; + tv.tv_usec = 0; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, SYSTEM_SOCKET_PATH, sizeof(SYSTEM_SOCKET_PATH)); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind system socket"); + goto failed; + } + + len = write(pipe, EMULATOR_SIGNAL, sizeof(EMULATOR_SIGNAL)); + + if (len != sizeof(EMULATOR_SIGNAL)) + goto failed; + + memset(buf, 0, sizeof(buf)); + + len = read(fd, buf, sizeof(buf)); + if (len <= 0 || strcmp(buf, "ctl.start=bluetoothd")) + goto failed; + + close(pipe); + close(fd); + return bluetoothd_start(hci_index); + +failed: + close(pipe); + if (fd >= 0) + close(fd); +} + +static int accept_connection(int sk) +{ + int err; + struct pollfd pfd; + int new_sk; + + memset(&pfd, 0 , sizeof(pfd)); + pfd.fd = sk; + pfd.events = POLLIN; + + err = poll(&pfd, 1, CONNECT_TIMEOUT); + if (err < 0) { + err = errno; + tester_warn("Failed to poll: %d (%s)", err, strerror(err)); + return -errno; + } + + if (err == 0) { + tester_warn("bluetoothd connect timeout"); + return -errno; + } + + new_sk = accept(sk, NULL, NULL); + if (new_sk < 0) { + err = errno; + tester_warn("Failed to accept socket: %d (%s)", + err, strerror(err)); + return -errno; + } + + return new_sk; +} + +static bool init_ipc(void) +{ + struct sockaddr_un addr; + + int sk; + int err; + + sk = socket(AF_LOCAL, SOCK_SEQPACKET, 0); + if (sk < 0) { + err = errno; + tester_warn("Failed to create socket: %d (%s)", err, + strerror(err)); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + memcpy(addr.sun_path, BLUEZ_HAL_SK_PATH, sizeof(BLUEZ_HAL_SK_PATH)); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = errno; + tester_warn("Failed to bind socket: %d (%s)", err, + strerror(err)); + close(sk); + return false; + } + + if (listen(sk, 2) < 0) { + err = errno; + tester_warn("Failed to listen on socket: %d (%s)", err, + strerror(err)); + close(sk); + return false; + } + + /* Start Android Bluetooth daemon service */ + if (property_set("ctl.start", SERVICE_NAME) < 0) { + tester_warn("Failed to start service %s", SERVICE_NAME); + close(sk); + return false; + } + + cmd_sk = accept_connection(sk); + if (cmd_sk < 0) { + close(sk); + return false; + } + + notif_sk = accept_connection(sk); + if (notif_sk < 0) { + close(sk); + close(cmd_sk); + cmd_sk = -1; + return false; + } + + tester_print("bluetoothd connected"); + + close(sk); + + return true; +} + +static void cleanup_ipc(void) +{ + if (cmd_sk < 0) + return; + + close(cmd_sk); + cmd_sk = -1; +} + +static gboolean check_for_daemon(gpointer user_data) +{ + int status; + struct test_data *data = user_data; + + if ((waitpid(data->bluetoothd_pid, &status, WNOHANG)) + != data->bluetoothd_pid) + return true; + + if (data->setup_done) { + if (WIFEXITED(status) && + (WEXITSTATUS(status) == EXIT_SUCCESS)) { + tester_test_passed(); + return false; + } + tester_test_failed(); + } else { + tester_setup_failed(); + test_post_teardown(data); + } + + tester_warn("Unexpected Daemon shutdown with status %d", status); + return false; +} + +static bool setup_module(int service_id) +{ + struct ipc_hdr response; + struct ipc_hdr expected_response; + + struct regmod_msg btmodule_msg = { + .header = { + .service_id = HAL_SERVICE_ID_CORE, + .opcode = HAL_OP_REGISTER_MODULE, + .len = sizeof(struct hal_cmd_register_module), + }, + .cmd = { + .service_id = service_id, + .mode = HAL_MODE_DEFAULT, + .max_clients = 1, + }, + }; + + if (write(cmd_sk, &btmodule_msg, sizeof(btmodule_msg)) < 0) + goto fail; + + if (read(cmd_sk, &response, sizeof(response)) < 0) + goto fail; + + expected_response = btmodule_msg.header; + expected_response.len = 0; + + if (memcmp(&response, &expected_response, sizeof(response)) == 0) + return true; + +fail: + tester_warn("Module registration failed."); + return false; +} + +static void setup(const void *data) +{ + const struct generic_data *generic_data = data; + struct test_data *test_data = tester_get_data(); + int signal_fd[2]; + char buf[1024]; + pid_t pid; + int len; + unsigned int i; + + if (pipe(signal_fd)) + goto failed; + + pid = fork(); + + if (pid < 0) { + close(signal_fd[0]); + close(signal_fd[1]); + goto failed; + } + + if (pid == 0) { + if (!tester_use_debug()) + fclose(stderr); + + close(signal_fd[0]); + emulator(signal_fd[1], test_data->mgmt_index); + exit(0); + } + + close(signal_fd[1]); + test_data->bluetoothd_pid = pid; + + len = read(signal_fd[0], buf, sizeof(buf)); + if (len <= 0 || (strcmp(buf, EMULATOR_SIGNAL))) { + close(signal_fd[0]); + goto failed; + } + + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, check_for_daemon, test_data, + NULL); + + if (!init_ipc()) { + tester_warn("Cannot initialize IPC mechanism!"); + goto failed; + } + tester_print("Will init %d services.", generic_data->num_services); + + for (i = 0; i < generic_data->num_services; i++) + if (!setup_module(generic_data->init_services[i])) { + cleanup_ipc(); + goto failed; + } + + test_data->setup_done = true; + + tester_setup_complete(); + return; + +failed: + g_idle_remove_by_data(test_data); + tester_setup_failed(); + test_post_teardown(data); +} + +static void teardown(const void *data) +{ + struct test_data *test_data = tester_get_data(); + + g_idle_remove_by_data(test_data); + cleanup_ipc(); + + if (test_data->bluetoothd_pid) + waitpid(test_data->bluetoothd_pid, NULL, 0); + + tester_teardown_complete(); +} + +static void ipc_send_tc(const void *data) +{ + const struct generic_data *generic_data = data; + const struct ipc_data *ipc_data = &generic_data->ipc_data; + + if (ipc_data->len) { + if (write(cmd_sk, ipc_data->buffer, ipc_data->len) < 0) + tester_test_failed(); + } +} + +#define service_data(args...) { args } + +#define gen_data(writelen, writebuf, servicelist...) \ + { \ + .ipc_data = { \ + .buffer = writebuf, \ + .len = writelen, \ + }, \ + .init_services = service_data(servicelist), \ + .num_services = sizeof((const int[]) \ + service_data(servicelist)) / \ + sizeof(int), \ + } + +#define test_generic(name, test, setup, teardown, buffer, writelen, \ + services...) \ + do { \ + struct test_data *user; \ + static const struct generic_data data = \ + gen_data(writelen, buffer, services); \ + user = g_malloc0(sizeof(struct test_data)); \ + if (!user) \ + break; \ + user->hciemu_type = HCIEMU_TYPE_BREDRLE; \ + tester_add_full(name, &data, test_pre_setup, setup, \ + test, teardown, test_post_teardown, \ + 3, user, g_free); \ + } while (0) + +#define test_opcode_valid(_name, _service, _opcode, _len, _servicelist...) \ + do { \ + static struct ipc_hdr hdr = { \ + .service_id = _service, \ + .opcode = _opcode, \ + .len = _len, \ + }; \ + \ + test_generic("Opcode out of range: "_name, \ + ipc_send_tc, setup, teardown, \ + &hdr, \ + sizeof(hdr), \ + _servicelist); \ + } while (0) + +struct vardata { + struct ipc_hdr hdr; + uint8_t buf[IPC_MTU]; +} __attribute__((packed)); + +#define test_datasize_valid(_name, _service, _opcode, _hlen, _addatasize, \ + _servicelist...) \ + do { \ + static struct vardata vdata = { \ + .hdr.service_id = _service, \ + .hdr.opcode = _opcode, \ + .hdr.len = (_hlen) + (_addatasize), \ + .buf = {}, \ + }; \ + test_generic("Data size "_name, \ + ipc_send_tc, setup, teardown, \ + &vdata, \ + sizeof(vdata.hdr) + (_hlen) + (_addatasize),\ + _servicelist); \ + } while (0) + +static struct regmod_msg register_bt_msg = { + .header = { + .service_id = HAL_SERVICE_ID_CORE, + .opcode = HAL_OP_REGISTER_MODULE, + .len = sizeof(struct hal_cmd_register_module), + }, + .cmd = { + .service_id = HAL_SERVICE_ID_BLUETOOTH, + }, +}; + +static struct regmod_msg register_bt_malformed_size_msg = { + .header = { + .service_id = HAL_SERVICE_ID_CORE, + .opcode = HAL_OP_REGISTER_MODULE, + /* wrong payload size declared */ + .len = sizeof(struct hal_cmd_register_module) - 1, + }, + .cmd = { + .service_id = HAL_SERVICE_ID_CORE, + }, +}; + +struct malformed_data3_struct { + struct regmod_msg valid_msg; + int redundant_data; +} __attribute__((packed)); + +static struct malformed_data3_struct malformed_data3_msg = { + /* valid register service message */ + .valid_msg = { + .header = { + .service_id = HAL_SERVICE_ID_CORE, + .opcode = HAL_OP_REGISTER_MODULE, + .len = sizeof(struct hal_cmd_register_module), + }, + .cmd = { + .service_id = HAL_SERVICE_ID_CORE, + }, + }, + /* plus redundant data */ + . redundant_data = 666, +}; + +static struct ipc_hdr enable_unknown_service_hdr = { + .service_id = HAL_SERVICE_ID_MAX + 1, + .opcode = HAL_OP_REGISTER_MODULE, + .len = 0, +}; + +static struct ipc_hdr enable_bt_service_hdr = { + .service_id = HAL_SERVICE_ID_BLUETOOTH, + .opcode = HAL_OP_ENABLE, + .len = 0, +}; + +struct bt_set_adapter_prop_data { + struct ipc_hdr hdr; + struct hal_cmd_set_adapter_prop prop; + + /* data placeholder for hal_cmd_set_adapter_prop.val[0] */ + uint8_t buf[IPC_MTU - sizeof(struct ipc_hdr) - + sizeof(struct hal_cmd_set_adapter_prop)]; +} __attribute__((packed)); + +#define set_name "new name" + +static struct bt_set_adapter_prop_data bt_set_adapter_prop_data_overs = { + .hdr.service_id = HAL_SERVICE_ID_BLUETOOTH, + .hdr.opcode = HAL_OP_SET_ADAPTER_PROP, + .hdr.len = sizeof(struct hal_cmd_set_adapter_prop) + sizeof(set_name), + + .prop.type = HAL_PROP_ADAPTER_NAME, + /* declare wrong descriptor length */ + .prop.len = sizeof(set_name) + 1, + /* init prop.val[0] */ + .buf = set_name, +}; + +static struct bt_set_adapter_prop_data bt_set_adapter_prop_data_unders = { + .hdr.service_id = HAL_SERVICE_ID_BLUETOOTH, + .hdr.opcode = HAL_OP_SET_ADAPTER_PROP, + .hdr.len = sizeof(struct hal_cmd_set_adapter_prop) + sizeof(set_name), + + .prop.type = HAL_PROP_ADAPTER_NAME, + /* declare wrong descriptor length */ + .prop.len = sizeof(set_name) - 1, + /* init prop.val[0] */ + .buf = set_name, +}; + +struct bt_set_remote_prop_data { + struct ipc_hdr hdr; + struct hal_cmd_set_remote_device_prop prop; + + /* data placeholder for hal_cmd_set_remote_device_prop.val[0] */ + uint8_t buf[IPC_MTU - sizeof(struct ipc_hdr) - + sizeof(struct hal_cmd_set_remote_device_prop)]; +} __attribute__((packed)); + +static struct bt_set_remote_prop_data bt_set_remote_prop_data_overs = { + .hdr.service_id = HAL_SERVICE_ID_BLUETOOTH, + .hdr.opcode = HAL_OP_SET_REMOTE_DEVICE_PROP, + .hdr.len = sizeof(struct hal_cmd_set_remote_device_prop) + + sizeof(set_name), + + .prop.bdaddr = {}, + .prop.type = HAL_PROP_DEVICE_NAME, + /* declare wrong descriptor length */ + .prop.len = sizeof(set_name) + 1, + .buf = set_name, +}; + +static struct bt_set_remote_prop_data bt_set_remote_prop_data_unders = { + .hdr.service_id = HAL_SERVICE_ID_BLUETOOTH, + .hdr.opcode = HAL_OP_SET_REMOTE_DEVICE_PROP, + .hdr.len = sizeof(struct hal_cmd_set_remote_device_prop) + + sizeof(set_name), + + .prop.bdaddr = {}, + .prop.type = HAL_PROP_DEVICE_NAME, + /* declare wrong descriptor length */ + .prop.len = sizeof(set_name) - 1, + .buf = set_name, +}; + +struct hidhost_set_info_data { + struct ipc_hdr hdr; + struct hal_cmd_hidhost_set_info info; + + /* data placeholder for hal_cmd_hidhost_set_info.descr[0] field */ + uint8_t buf[IPC_MTU - sizeof(struct ipc_hdr) - + sizeof(struct hal_cmd_hidhost_set_info)]; +} __attribute__((packed)); + +#define set_info_data "some descriptor" + +static struct hidhost_set_info_data hidhost_set_info_data_overs = { + .hdr.service_id = HAL_SERVICE_ID_HIDHOST, + .hdr.opcode = HAL_OP_HIDHOST_SET_INFO, + .hdr.len = sizeof(struct hal_cmd_hidhost_set_info) + + sizeof(set_info_data), + + /* declare wrong descriptor length */ + .info.descr_len = sizeof(set_info_data) + 1, + /* init .info.descr[0] */ + .buf = set_info_data, +}; + +static struct hidhost_set_info_data hidhost_set_info_data_unders = { + .hdr.service_id = HAL_SERVICE_ID_HIDHOST, + .hdr.opcode = HAL_OP_HIDHOST_SET_INFO, + .hdr.len = sizeof(struct hal_cmd_hidhost_set_info) + + sizeof(set_info_data), + + /* declare wrong descriptor length */ + .info.descr_len = sizeof(set_info_data) - 1, + /* init .info.descr[0] */ + .buf = set_info_data, +}; + +struct hidhost_set_report_data { + struct ipc_hdr hdr; + struct hal_cmd_hidhost_set_report report; + + /* data placeholder for hal_cmd_hidhost_set_report.data[0] field */ + uint8_t buf[IPC_MTU - sizeof(struct ipc_hdr) - + sizeof(struct hal_cmd_hidhost_set_report)]; +} __attribute__((packed)); + +#define set_rep_data "1234567890" + +static struct hidhost_set_report_data hidhost_set_report_data_overs = { + .hdr.service_id = HAL_SERVICE_ID_HIDHOST, + .hdr.opcode = HAL_OP_HIDHOST_SET_REPORT, + .hdr.len = sizeof(struct hal_cmd_hidhost_set_report) + + sizeof(set_rep_data), + + /* declare wrong descriptor length */ + .report.len = sizeof(set_rep_data) + 1, + /* init report.data[0] */ + .buf = set_rep_data, +}; + +static struct hidhost_set_report_data hidhost_set_report_data_unders = { + .hdr.service_id = HAL_SERVICE_ID_HIDHOST, + .hdr.opcode = HAL_OP_HIDHOST_SET_REPORT, + .hdr.len = sizeof(struct hal_cmd_hidhost_set_report) + + sizeof(set_rep_data), + + /* declare wrong descriptor length */ + .report.len = sizeof(set_rep_data) - 1, + /* init report.data[0] */ + .buf = set_rep_data, +}; + +struct hidhost_send_data_data { + struct ipc_hdr hdr; + struct hal_cmd_hidhost_send_data hiddata; + + /* data placeholder for hal_cmd_hidhost_send_data.data[0] field */ + uint8_t buf[IPC_MTU - sizeof(struct ipc_hdr) - + sizeof(struct hal_cmd_hidhost_send_data)]; +} __attribute__((packed)); + +#define send_data_data "1234567890" + +static struct hidhost_send_data_data hidhost_send_data_overs = { + .hdr.service_id = HAL_SERVICE_ID_HIDHOST, + .hdr.opcode = HAL_OP_HIDHOST_SEND_DATA, + .hdr.len = sizeof(struct hal_cmd_hidhost_send_data) + + sizeof(send_data_data), + + /* declare wrong descriptor length */ + .hiddata.len = sizeof(send_data_data) + 1, + /* init .hiddata.data[0] */ + .buf = send_data_data, +}; + +static struct hidhost_send_data_data hidhost_send_data_unders = { + .hdr.service_id = HAL_SERVICE_ID_HIDHOST, + .hdr.opcode = HAL_OP_HIDHOST_SEND_DATA, + .hdr.len = sizeof(struct hal_cmd_hidhost_send_data) + + sizeof(send_data_data), + + /* declare wrong descriptor length */ + .hiddata.len = sizeof(send_data_data) - 1, + /* init .hiddata.data[0] */ + .buf = send_data_data, +}; + +#define hfp_number "#1234567890" + +struct hfp_dial_data { + struct ipc_hdr hdr; + struct hal_cmd_hf_client_dial data; + + uint8_t buf[IPC_MTU - sizeof(struct ipc_hdr) - + sizeof(struct hal_cmd_hf_client_dial)]; +} __attribute__((packed)); + +static struct hfp_dial_data hfp_dial_overs = { + .hdr.service_id = HAL_SERVICE_ID_HANDSFREE_CLIENT, + .hdr.opcode = HAL_OP_HF_CLIENT_DIAL, + .hdr.len = sizeof(struct hal_cmd_hf_client_dial) + sizeof(hfp_number), + + .data.number_len = sizeof(hfp_number) + 1, + .buf = hfp_number, +}; + +static struct hfp_dial_data hfp_dial_unders = { + .hdr.service_id = HAL_SERVICE_ID_HANDSFREE_CLIENT, + .hdr.opcode = HAL_OP_HF_CLIENT_DIAL, + .hdr.len = sizeof(struct hal_cmd_hf_client_dial) + sizeof(hfp_number), + + .data.number_len = sizeof(hfp_number) - 1, + .buf = hfp_number, +}; + +int main(int argc, char *argv[]) +{ + snprintf(exec_dir, sizeof(exec_dir), "%s", dirname(argv[0])); + + tester_init(&argc, &argv); + + /* check general IPC errors */ + test_generic("Too small data", + ipc_send_tc, setup, teardown, + ®ister_bt_msg, 1); + + test_generic("Malformed data (wrong payload declared)", + ipc_send_tc, setup, teardown, + ®ister_bt_malformed_size_msg, + sizeof(register_bt_malformed_size_msg), + HAL_SERVICE_ID_BLUETOOTH); + + test_generic("Malformed data2 (undersized msg)", + ipc_send_tc, setup, teardown, + ®ister_bt_msg, + sizeof(register_bt_msg) - 1, + HAL_SERVICE_ID_BLUETOOTH); + + test_generic("Malformed data3 (oversized msg)", + ipc_send_tc, setup, teardown, + &malformed_data3_msg, + sizeof(malformed_data3_msg), + HAL_SERVICE_ID_BLUETOOTH); + + test_generic("Invalid service", + ipc_send_tc, setup, teardown, + &enable_unknown_service_hdr, + sizeof(enable_unknown_service_hdr), + HAL_SERVICE_ID_BLUETOOTH); + + test_generic("Enable unregistered service", + ipc_send_tc, setup, teardown, + &enable_bt_service_hdr, + sizeof(enable_bt_service_hdr)); + + /* check service handler's max opcode value */ + test_opcode_valid("CORE", HAL_SERVICE_ID_CORE, 0x03, 0); + + test_opcode_valid("BLUETOOTH", HAL_SERVICE_ID_BLUETOOTH, 0x15, 0, + HAL_SERVICE_ID_BLUETOOTH); + + test_opcode_valid("SOCK", HAL_SERVICE_ID_SOCKET, 0x03, 0, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_SOCKET); + + test_opcode_valid("HIDHOST", HAL_SERVICE_ID_HIDHOST, 0x10, 0, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + + test_opcode_valid("PAN", HAL_SERVICE_ID_PAN, 0x05, 0, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + + test_opcode_valid("HANDSFREE", HAL_SERVICE_ID_HANDSFREE, 0x10, 0, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE); + + test_opcode_valid("A2DP", HAL_SERVICE_ID_A2DP, 0x03, 0, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_A2DP); + + test_opcode_valid("HEALTH", HAL_SERVICE_ID_HEALTH, 0x06, 0, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HEALTH); + + test_opcode_valid("AVRCP", HAL_SERVICE_ID_AVRCP, 0x0b, 0, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_AVRCP); + + test_opcode_valid("GATT", HAL_SERVICE_ID_GATT, 0x24, 0, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_GATT); + + test_opcode_valid("HF_CLIENT", HAL_SERVICE_ID_HANDSFREE_CLIENT, 0x10, 0, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + + test_opcode_valid("MAP_CLIENT", HAL_SERVICE_ID_MAP_CLIENT, 0x01, 0, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_MAP_CLIENT); + + /* check for valid data size */ + test_datasize_valid("CORE Register+", HAL_SERVICE_ID_CORE, + HAL_OP_REGISTER_MODULE, + sizeof(struct hal_cmd_register_module), 1); + test_datasize_valid("CORE Register-", HAL_SERVICE_ID_CORE, + HAL_OP_REGISTER_MODULE, + sizeof(struct hal_cmd_register_module), -1); + test_datasize_valid("CORE Unregister+", HAL_SERVICE_ID_CORE, + HAL_OP_UNREGISTER_MODULE, + sizeof(struct hal_cmd_unregister_module), 1); + test_datasize_valid("CORE Unregister-", HAL_SERVICE_ID_CORE, + HAL_OP_UNREGISTER_MODULE, + sizeof(struct hal_cmd_unregister_module), -1); + + /* check for valid data size for BLUETOOTH */ + test_datasize_valid("BT Enable+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_ENABLE, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Disable+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_DISABLE, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Adapter Props+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_ADAPTER_PROPS, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Adapter Prop+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_ADAPTER_PROP, + sizeof(struct hal_cmd_get_adapter_prop), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Adapter Prop-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_ADAPTER_PROP, + sizeof(struct hal_cmd_get_adapter_prop), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Set Adapter Prop+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SET_ADAPTER_PROP, + sizeof(struct hal_cmd_set_adapter_prop), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Set Adapter Prop-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SET_ADAPTER_PROP, + sizeof(struct hal_cmd_set_adapter_prop), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_generic("Data size BT Set Adapter Prop Vardata+", + ipc_send_tc, setup, teardown, + &bt_set_adapter_prop_data_overs, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_set_adapter_prop) + + sizeof(set_name)), + HAL_SERVICE_ID_BLUETOOTH); + test_generic("Data size BT Set Adapter Prop Vardata+", + ipc_send_tc, setup, teardown, + &bt_set_adapter_prop_data_unders, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_set_adapter_prop) + + sizeof(set_name)), + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote Props+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROPS, + sizeof(struct hal_cmd_get_remote_device_props), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote Props-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROPS, + sizeof(struct hal_cmd_get_remote_device_props), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote Prop+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROP, + sizeof(struct hal_cmd_get_remote_device_prop), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote Prop-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_DEVICE_PROP, + sizeof(struct hal_cmd_get_remote_device_prop), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Set Remote Prop+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SET_REMOTE_DEVICE_PROP, + sizeof(struct hal_cmd_set_remote_device_prop), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Set Remote Prop-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SET_REMOTE_DEVICE_PROP, + sizeof(struct hal_cmd_set_remote_device_prop), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_generic("Data size BT Set Remote Prop Vardata+", + ipc_send_tc, setup, teardown, + &bt_set_remote_prop_data_overs, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_set_remote_device_prop) + + sizeof(set_name)), + HAL_SERVICE_ID_BLUETOOTH); + test_generic("Data size BT Set Remote Prop Vardata-", + ipc_send_tc, setup, teardown, + &bt_set_remote_prop_data_unders, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_set_remote_device_prop) + + sizeof(set_name)), + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote SV Rec+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICE_REC, + sizeof(struct hal_cmd_get_remote_service_rec), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote SV Rec-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICE_REC, + sizeof(struct hal_cmd_get_remote_service_rec), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote Services+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICES, + sizeof(struct hal_cmd_get_remote_services), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Get Remote Services-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_GET_REMOTE_SERVICES, + sizeof(struct hal_cmd_get_remote_services), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Start Discovery+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_START_DISCOVERY, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Cancel Discovery+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_CANCEL_DISCOVERY, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Create Bond+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_CREATE_BOND, + sizeof(struct hal_cmd_create_bond), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Create Bond-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_CREATE_BOND, + sizeof(struct hal_cmd_create_bond), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Remove Bond+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_REMOVE_BOND, + sizeof(struct hal_cmd_remove_bond), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Remove Bond-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_REMOVE_BOND, + sizeof(struct hal_cmd_remove_bond), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Cancel Bond+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_CANCEL_BOND, + sizeof(struct hal_cmd_cancel_bond), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Cancel Bond-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_CANCEL_BOND, + sizeof(struct hal_cmd_cancel_bond), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Pin Reply+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_PIN_REPLY, + sizeof(struct hal_cmd_pin_reply), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT Pin Reply-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_PIN_REPLY, + sizeof(struct hal_cmd_pin_reply), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT SSP Reply+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SSP_REPLY, + sizeof(struct hal_cmd_ssp_reply), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT SSP Reply-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_SSP_REPLY, + sizeof(struct hal_cmd_ssp_reply), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT DUT Mode Conf+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_DUT_MODE_CONF, + sizeof(struct hal_cmd_dut_mode_conf), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT DUT Mode Conf-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_DUT_MODE_CONF, + sizeof(struct hal_cmd_dut_mode_conf), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT DUT Mode Send+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_DUT_MODE_SEND, + sizeof(struct hal_cmd_dut_mode_send), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT DUT Mode Send-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_DUT_MODE_SEND, + sizeof(struct hal_cmd_dut_mode_send), -1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT LE Test+", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_LE_TEST_MODE, + sizeof(struct hal_cmd_le_test_mode), 1, + HAL_SERVICE_ID_BLUETOOTH); + test_datasize_valid("BT LE Test-", HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_LE_TEST_MODE, + sizeof(struct hal_cmd_le_test_mode), -1, + HAL_SERVICE_ID_BLUETOOTH); + + /* check for valid data size for SOCK */ + test_datasize_valid("SOCKET Listen+", HAL_SERVICE_ID_SOCKET, + HAL_OP_SOCKET_LISTEN, + sizeof(struct hal_cmd_socket_listen), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_SOCKET); + test_datasize_valid("SOCKET Listen-", HAL_SERVICE_ID_SOCKET, + HAL_OP_SOCKET_LISTEN, + sizeof(struct hal_cmd_socket_listen), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_SOCKET); + test_datasize_valid("SOCKET Connect+", HAL_SERVICE_ID_SOCKET, + HAL_OP_SOCKET_CONNECT, + sizeof(struct hal_cmd_socket_connect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_SOCKET); + test_datasize_valid("SOCKET Connect-", HAL_SERVICE_ID_SOCKET, + HAL_OP_SOCKET_CONNECT, + sizeof(struct hal_cmd_socket_connect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_SOCKET); + + /* check for valid data size for HID Host */ + test_datasize_valid("HIDHOST Connect+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_CONNECT, + sizeof(struct hal_cmd_hidhost_connect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Connect-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_CONNECT, + sizeof(struct hal_cmd_hidhost_connect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Disconnect+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_DISCONNECT, + sizeof(struct hal_cmd_hidhost_disconnect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Disconnect-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_DISCONNECT, + sizeof(struct hal_cmd_hidhost_disconnect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Virt. Unplug+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_VIRTUAL_UNPLUG, + sizeof(struct hal_cmd_hidhost_virtual_unplug), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Virt. Unplug-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_VIRTUAL_UNPLUG, + sizeof(struct hal_cmd_hidhost_virtual_unplug), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Set Info+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_INFO, + sizeof(struct hal_cmd_hidhost_set_info), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Set Info-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_INFO, + sizeof(struct hal_cmd_hidhost_set_info), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_generic("Data size HIDHOST Set Info Vardata+", + ipc_send_tc, setup, teardown, + &hidhost_set_info_data_overs, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hidhost_set_info) + + sizeof(set_info_data)), + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_generic("Data size HIDHOST Set Info Vardata-", + ipc_send_tc, setup, teardown, + &hidhost_set_info_data_unders, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hidhost_set_info) + + sizeof(set_info_data)), + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Get Protocol+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_GET_PROTOCOL, + sizeof(struct hal_cmd_hidhost_get_protocol), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Get Protocol-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_GET_PROTOCOL, + sizeof(struct hal_cmd_hidhost_get_protocol), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Set Protocol+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_PROTOCOL, + sizeof(struct hal_cmd_hidhost_set_protocol), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Set Protocol-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_PROTOCOL, + sizeof(struct hal_cmd_hidhost_set_protocol), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Get Report+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_GET_REPORT, + sizeof(struct hal_cmd_hidhost_get_report), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Get Report-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_GET_REPORT, + sizeof(struct hal_cmd_hidhost_get_report), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Set Report+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_REPORT, + sizeof(struct hal_cmd_hidhost_set_report), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Set Report-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SET_REPORT, + sizeof(struct hal_cmd_hidhost_set_report), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_generic("Data size HIDHOST Set Report Vardata+", + ipc_send_tc, setup, teardown, + &hidhost_set_report_data_overs, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hidhost_set_report) + + sizeof(set_rep_data)), + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_generic("Data size HIDHOST Set Report Vardata-", + ipc_send_tc, setup, teardown, + &hidhost_set_report_data_unders, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hidhost_set_report) + + sizeof(set_rep_data)), + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Send Data+", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SEND_DATA, + sizeof(struct hal_cmd_hidhost_send_data), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_datasize_valid("HIDHOST Send Data-", HAL_SERVICE_ID_HIDHOST, + HAL_OP_HIDHOST_SEND_DATA, + sizeof(struct hal_cmd_hidhost_send_data), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_generic("Data size HIDHOST Send Vardata+", + ipc_send_tc, setup, teardown, + &hidhost_send_data_overs, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hidhost_send_data) + + sizeof(send_data_data)), + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + test_generic("Data size HIDHOST Send Vardata-", + ipc_send_tc, setup, teardown, + &hidhost_send_data_unders, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hidhost_send_data) + + sizeof(send_data_data)), + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_HIDHOST); + + /* check for valid data size for PAN */ + test_datasize_valid("PAN Enable+", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_ENABLE, + sizeof(struct hal_cmd_pan_enable), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + test_datasize_valid("PAN Enable-", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_ENABLE, + sizeof(struct hal_cmd_pan_enable), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + test_datasize_valid("PAN Get Role+", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_GET_ROLE, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + test_datasize_valid("PAN Connect+", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_CONNECT, + sizeof(struct hal_cmd_pan_connect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + test_datasize_valid("PAN Connect-", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_CONNECT, + sizeof(struct hal_cmd_pan_connect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + test_datasize_valid("PAN Disconnect+", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_DISCONNECT, + sizeof(struct hal_cmd_pan_disconnect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + test_datasize_valid("PAN Disconnect-", HAL_SERVICE_ID_PAN, + HAL_OP_PAN_DISCONNECT, + sizeof(struct hal_cmd_pan_disconnect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_PAN); + + /* check for valid data size for A2DP */ + test_datasize_valid("A2DP Connect+", HAL_SERVICE_ID_A2DP, + HAL_OP_A2DP_CONNECT, + sizeof(struct hal_cmd_a2dp_connect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_A2DP); + test_datasize_valid("A2DP Connect-", HAL_SERVICE_ID_A2DP, + HAL_OP_A2DP_CONNECT, + sizeof(struct hal_cmd_a2dp_connect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_A2DP); + test_datasize_valid("A2DP Disconnect+", HAL_SERVICE_ID_A2DP, + HAL_OP_A2DP_DISCONNECT, + sizeof(struct hal_cmd_a2dp_disconnect), 1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_A2DP); + test_datasize_valid("A2DP Disconnect-", HAL_SERVICE_ID_A2DP, + HAL_OP_A2DP_DISCONNECT, + sizeof(struct hal_cmd_a2dp_disconnect), -1, + HAL_SERVICE_ID_BLUETOOTH, HAL_SERVICE_ID_A2DP); + + /* Check for valid data size for Handsfree Client */ + test_datasize_valid("HF_CLIENT Connect+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT, + sizeof(struct hal_cmd_hf_client_connect), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Connect-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT, + sizeof(struct hal_cmd_hf_client_connect), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Disconnect+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT, + sizeof(struct hal_cmd_hf_client_disconnect), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Disconnect-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT, + sizeof(struct hal_cmd_hf_client_disconnect), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Connect Audio+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT_AUDIO, + sizeof(struct hal_cmd_hf_client_connect_audio), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Connect Audio-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CONNECT_AUDIO, + sizeof(struct hal_cmd_hf_client_connect_audio), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Disconnect Audio+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT_AUDIO, + sizeof(struct hal_cmd_hf_client_disconnect_audio), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Disconnect Audio-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DISCONNECT_AUDIO, + sizeof(struct hal_cmd_hf_client_disconnect_audio), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Start VR+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_START_VR, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Start VR-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_START_VR, + 0, -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Stop VR+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_STOP_VR, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Stop VR-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_STOP_VR, + 0, -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Vol Contr.+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_VOLUME_CONTROL, + sizeof(struct hal_cmd_hf_client_volume_control), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Vol Contr.-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_VOLUME_CONTROL, + sizeof(struct hal_cmd_hf_client_volume_control), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_generic("Data size HF_CLIENT Dial Vardata+", + ipc_send_tc, setup, teardown, + &hfp_dial_overs, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hf_client_dial) + + sizeof(hfp_number)), + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_generic("Data size HF_CLIENT Dial Vardata-", + ipc_send_tc, setup, teardown, + &hfp_dial_unders, + (sizeof(struct ipc_hdr) + + sizeof(struct hal_cmd_hf_client_dial) + + sizeof(hfp_number)), + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Dial Memory+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DIAL_MEMORY, + sizeof(struct hal_cmd_hf_client_dial_memory), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Dial Memory-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_DIAL_MEMORY, + sizeof(struct hal_cmd_hf_client_dial_memory), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Call Action+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CALL_ACTION, + sizeof(struct hal_cmd_hf_client_call_action), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Call Action-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_CALL_ACTION, + sizeof(struct hal_cmd_hf_client_call_action), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Query Current Calls+", + HAL_SERVICE_ID_BLUETOOTH, + HAL_OP_HF_CLIENT_QUERY_CURRENT_CALLS, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Query Current Calls-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_CURRENT_CALLS, + 0, -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Query Operator Name+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_OPERATOR_NAME, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Query Operator Name-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_QUERY_OPERATOR_NAME, + 0, -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Retrieve Subscrb. Info+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_RETRIEVE_SUBSCR_INFO, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Retrieve Subscrb. Info-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_RETRIEVE_SUBSCR_INFO, + 0, -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Send DTMF+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_SEND_DTMF, + sizeof(struct hal_cmd_hf_client_send_dtmf), 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Send DTMF-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_SEND_DTMF, + sizeof(struct hal_cmd_hf_client_send_dtmf), -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Get Last Voice Tag+", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_GET_LAST_VOICE_TAG_NUM, + 0, 1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + test_datasize_valid("HF_CLIENT Get Last Voice Tag-", + HAL_SERVICE_ID_HANDSFREE_CLIENT, + HAL_OP_HF_CLIENT_GET_LAST_VOICE_TAG_NUM, + 0, -1, + HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_HANDSFREE_CLIENT); + + /* check for valid data size for MAP CLIENT */ + test_datasize_valid("MAP CLIENT Get instances+", + HAL_SERVICE_ID_MAP_CLIENT, + HAL_OP_MAP_CLIENT_GET_INSTANCES, + sizeof(struct hal_cmd_map_client_get_instances), + 1, HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_MAP_CLIENT); + test_datasize_valid("MAP CLIENT Get instances-", + HAL_SERVICE_ID_MAP_CLIENT, + HAL_OP_MAP_CLIENT_GET_INSTANCES, + sizeof(struct hal_cmd_map_client_get_instances), + -1, HAL_SERVICE_ID_BLUETOOTH, + HAL_SERVICE_ID_MAP_CLIENT); + + return tester_run(); +}
diff --git a/android/ipc.c b/android/ipc.c new file mode 100644 index 0000000..2e67428 --- /dev/null +++ b/android/ipc.c
@@ -0,0 +1,437 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stddef.h> +#include <errno.h> +#include <stdint.h> +#include <string.h> +#include <signal.h> +#include <stdbool.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <glib.h> + +#include "ipc-common.h" +#include "ipc.h" +#include "src/log.h" + +struct service_handler { + const struct ipc_handler *handler; + uint8_t size; +}; + +struct ipc { + struct service_handler *services; + int service_max; + + const char *path; + size_t size; + + GIOChannel *cmd_io; + guint cmd_watch; + + bool notifications; + GIOChannel *notif_io; + guint notif_watch; + + ipc_disconnect_cb disconnect_cb; + void *disconnect_cb_data; +}; + +static void ipc_disconnect(struct ipc *ipc, bool in_cleanup) +{ + if (ipc->cmd_watch) { + g_source_remove(ipc->cmd_watch); + ipc->cmd_watch = 0; + } + + if (ipc->cmd_io) { + g_io_channel_shutdown(ipc->cmd_io, TRUE, NULL); + g_io_channel_unref(ipc->cmd_io); + ipc->cmd_io = NULL; + } + + if (ipc->notif_watch) { + g_source_remove(ipc->notif_watch); + ipc->notif_watch = 0; + } + + if (ipc->notif_io) { + g_io_channel_shutdown(ipc->notif_io, TRUE, NULL); + g_io_channel_unref(ipc->notif_io); + ipc->notif_io = NULL; + } + + if (in_cleanup) + return; + + if (ipc->disconnect_cb) + ipc->disconnect_cb(ipc->disconnect_cb_data); +} + +static int ipc_handle_msg(struct service_handler *handlers, size_t max_index, + const void *buf, ssize_t len) +{ + const struct ipc_hdr *msg = buf; + const struct ipc_handler *handler; + + if (len < (ssize_t) sizeof(*msg)) { + DBG("message too small (%zd bytes)", len); + return -EBADMSG; + } + + if (len != (ssize_t) (sizeof(*msg) + msg->len)) { + DBG("message malformed (%zd bytes)", len); + return -EBADMSG; + } + + /* if service is valid */ + if (msg->service_id > max_index) { + DBG("unknown service (0x%x)", msg->service_id); + return -EOPNOTSUPP; + } + + /* if service is registered */ + if (!handlers[msg->service_id].handler) { + DBG("service not registered (0x%x)", msg->service_id); + return -EOPNOTSUPP; + } + + /* if opcode is valid */ + if (msg->opcode == IPC_OP_STATUS || + msg->opcode > handlers[msg->service_id].size) { + DBG("invalid opcode 0x%x for service 0x%x", msg->opcode, + msg->service_id); + return -EOPNOTSUPP; + } + + /* opcode is table offset + 1 */ + handler = &handlers[msg->service_id].handler[msg->opcode - 1]; + + /* if payload size is valid */ + if ((handler->var_len && handler->data_len > msg->len) || + (!handler->var_len && handler->data_len != msg->len)) { + DBG("invalid size for opcode 0x%x service 0x%x", + msg->opcode, msg->service_id); + return -EMSGSIZE; + } + + handler->handler(msg->payload, msg->len); + + return 0; +} + +static gboolean cmd_watch_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct ipc *ipc = user_data; + + char buf[IPC_MTU]; + ssize_t ret; + int fd, err; + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + info("IPC: command socket closed"); + + ipc->cmd_watch = 0; + goto fail; + } + + fd = g_io_channel_unix_get_fd(io); + + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) { + error("IPC: command read failed (%s)", strerror(errno)); + goto fail; + } + + err = ipc_handle_msg(ipc->services, ipc->service_max, buf, ret); + if (err < 0) { + error("IPC: failed to handle message (%s)", strerror(-err)); + goto fail; + } + + return TRUE; + +fail: + ipc_disconnect(ipc, false); + + return FALSE; +} + +static gboolean notif_watch_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct ipc *ipc = user_data; + + info("IPC: notification socket closed"); + + ipc->notif_watch = 0; + + ipc_disconnect(ipc, false); + + return FALSE; +} + +static GIOChannel *ipc_connect(const char *path, size_t size, + GIOFunc connect_cb, void *user_data) +{ + struct sockaddr_un addr; + GIOCondition cond; + GIOChannel *io; + int sk; + + sk = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (sk < 0) { + error("IPC: failed to create socket: %d (%s)", errno, + strerror(errno)); + return NULL; + } + + io = g_io_channel_unix_new(sk); + + g_io_channel_set_close_on_unref(io, TRUE); + g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + memcpy(addr.sun_path, path, size); + + connect(sk, (struct sockaddr *) &addr, sizeof(addr)); + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + g_io_add_watch(io, cond, connect_cb, user_data); + + return io; +} + +static gboolean notif_connect_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct ipc *ipc = user_data; + + DBG(""); + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + error("IPC: notification socket connect failed"); + + ipc_disconnect(ipc, false); + + return FALSE; + } + + cond = G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + ipc->notif_watch = g_io_add_watch(io, cond, notif_watch_cb, ipc); + + cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + ipc->cmd_watch = g_io_add_watch(ipc->cmd_io, cond, cmd_watch_cb, ipc); + + info("IPC: successfully connected (with notifications)"); + + return FALSE; +} + +static gboolean cmd_connect_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct ipc *ipc = user_data; + + DBG(""); + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + error("IPC: command socket connect failed"); + ipc_disconnect(ipc, false); + + return FALSE; + } + + if (ipc->notifications) { + ipc->notif_io = ipc_connect(ipc->path, ipc->size, + notif_connect_cb, ipc); + if (!ipc->notif_io) + ipc_disconnect(ipc, false); + + return FALSE; + } + + cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + ipc->cmd_watch = g_io_add_watch(ipc->cmd_io, cond, cmd_watch_cb, ipc); + + info("IPC: successfully connected (without notifications)"); + + return FALSE; +} + +struct ipc *ipc_init(const char *path, size_t size, int max_service_id, + bool notifications, + ipc_disconnect_cb cb, void *cb_data) +{ + struct ipc *ipc; + + ipc = g_new0(struct ipc, 1); + + ipc->services = g_new0(struct service_handler, max_service_id + 1); + ipc->service_max = max_service_id; + + ipc->path = path; + ipc->size = size; + + ipc->notifications = notifications; + + ipc->cmd_io = ipc_connect(path, size, cmd_connect_cb, ipc); + if (!ipc->cmd_io) { + g_free(ipc->services); + g_free(ipc); + return NULL; + } + + ipc->disconnect_cb = cb; + ipc->disconnect_cb_data = cb_data; + + return ipc; +} + +void ipc_cleanup(struct ipc *ipc) +{ + ipc_disconnect(ipc, true); + + g_free(ipc->services); + g_free(ipc); +} + +static void ipc_send(int sk, uint8_t service_id, uint8_t opcode, uint16_t len, + void *param, int fd) +{ + struct msghdr msg; + struct iovec iv[2]; + struct ipc_hdr m; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + + memset(&msg, 0, sizeof(msg)); + memset(&m, 0, sizeof(m)); + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + + m.service_id = service_id; + m.opcode = opcode; + m.len = len; + + iv[0].iov_base = &m; + iv[0].iov_len = sizeof(m); + + iv[1].iov_base = param; + iv[1].iov_len = len; + + msg.msg_iov = iv; + msg.msg_iovlen = 2; + + if (fd >= 0) { + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + cmsg = CMSG_FIRSTHDR(&msg); + 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)); + } + + if (sendmsg(sk, &msg, 0) < 0) { + error("IPC send failed :%s", strerror(errno)); + + /* TODO disconnect IPC here when this function becomes static */ + raise(SIGTERM); + } +} + +void ipc_send_rsp(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint8_t status) +{ + struct ipc_status s; + int sk; + + sk = g_io_channel_unix_get_fd(ipc->cmd_io); + + if (status == IPC_STATUS_SUCCESS) { + ipc_send(sk, service_id, opcode, 0, NULL, -1); + return; + } + + s.code = status; + + ipc_send(sk, service_id, IPC_OP_STATUS, sizeof(s), &s, -1); +} + +void ipc_send_rsp_full(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint16_t len, void *param, int fd) +{ + ipc_send(g_io_channel_unix_get_fd(ipc->cmd_io), service_id, opcode, len, + param, fd); +} + +void ipc_send_notif(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint16_t len, void *param) +{ + return ipc_send_notif_with_fd(ipc, service_id, opcode, len, param, -1); +} + +void ipc_send_notif_with_fd(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint16_t len, void *param, int fd) +{ + if (!ipc || !ipc->notif_io) + return; + + ipc_send(g_io_channel_unix_get_fd(ipc->notif_io), service_id, opcode, + len, param, fd); +} + +void ipc_register(struct ipc *ipc, uint8_t service, + const struct ipc_handler *handlers, uint8_t size) +{ + if (service > ipc->service_max) + return; + + ipc->services[service].handler = handlers; + ipc->services[service].size = size; +} + +void ipc_unregister(struct ipc *ipc, uint8_t service) +{ + if (service > ipc->service_max) + return; + + ipc->services[service].handler = NULL; + ipc->services[service].size = 0; +}
diff --git a/android/ipc.h b/android/ipc.h new file mode 100644 index 0000000..fd2b985 --- /dev/null +++ b/android/ipc.h
@@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +struct ipc_handler { + void (*handler) (const void *buf, uint16_t len); + bool var_len; + size_t data_len; +}; + +struct ipc; + +typedef void (*ipc_disconnect_cb) (void *data); + +struct ipc *ipc_init(const char *path, size_t size, int max_service_id, + bool notifications, + ipc_disconnect_cb cb, void *cb_data); +void ipc_cleanup(struct ipc *ipc); + +void ipc_send_rsp(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint8_t status); +void ipc_send_rsp_full(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint16_t len, void *param, int fd); +void ipc_send_notif(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint16_t len, void *param); +void ipc_send_notif_with_fd(struct ipc *ipc, uint8_t service_id, uint8_t opcode, + uint16_t len, void *param, int fd); + +void ipc_register(struct ipc *ipc, uint8_t service, + const struct ipc_handler *handlers, uint8_t size); +void ipc_unregister(struct ipc *ipc, uint8_t service);
diff --git a/android/log.c b/android/log.c new file mode 100644 index 0000000..35917c6 --- /dev/null +++ b/android/log.c
@@ -0,0 +1,216 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <time.h> +#include <sys/uio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "src/log.h" + +#define LOG_TAG "bluetoothd" + +#define LOG_DEBUG 3 +#define LOG_INFO 4 +#define LOG_WARN 5 +#define LOG_ERR 6 + +#define LOG_ID_SYSTEM 3 + +struct logd_header { + uint8_t id; + uint16_t pid; /* Android logd expects only 2 bytes for PID */ + uint32_t sec; + uint32_t nsec; +} __attribute__ ((packed)); + +static int log_fd = -1; +static bool legacy_log = false; + +static void android_log(unsigned char level, const char *fmt, va_list ap) +{ + struct logd_header header; + struct iovec vec[4]; + int cnt = 0; + char *msg; + static pid_t pid = 0; + + if (log_fd < 0) + return; + + /* no need to call getpid all the time since we don't fork */ + if (!pid) + pid = getpid(); + + if (vasprintf(&msg, fmt, ap) < 0) + return; + + if (!legacy_log) { + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + + header.id = LOG_ID_SYSTEM; + header.pid = pid; + header.sec = ts.tv_sec; + header.nsec = ts.tv_nsec; + + vec[0].iov_base = &header; + vec[0].iov_len = sizeof(header); + + cnt += 1; + } + + vec[cnt + 0].iov_base = &level; + vec[cnt + 0].iov_len = sizeof(level); + vec[cnt + 1].iov_base = LOG_TAG; + vec[cnt + 1].iov_len = sizeof(LOG_TAG); + vec[cnt + 2].iov_base = msg; + vec[cnt + 2].iov_len = strlen(msg) + 1; + + cnt += 3; + + writev(log_fd, vec, cnt); + + free(msg); +} + +void info(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + android_log(LOG_INFO, format, ap); + + va_end(ap); +} + +void warn(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + android_log(LOG_WARN, format, ap); + + va_end(ap); +} + +void error(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + android_log(LOG_ERR, format, ap); + + va_end(ap); +} + +void btd_debug(uint16_t index, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + android_log(LOG_DEBUG, format, ap); + + va_end(ap); +} + +static bool init_legacy_log(void) +{ + log_fd = open("/dev/log/system", O_WRONLY); + if (log_fd < 0) + return false; + + legacy_log = true; + + return true; +} + +static bool init_logd(void) +{ + struct sockaddr_un addr; + + log_fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (log_fd < 0) + return false; + + if (fcntl(log_fd, F_SETFL, O_NONBLOCK) < 0) + goto failed; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, "/dev/socket/logdw"); + + if (connect(log_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + goto failed; + + return true; + +failed: + close(log_fd); + log_fd = -1; + + return false; +} + +extern struct btd_debug_desc __start___debug[]; +extern struct btd_debug_desc __stop___debug[]; + +void __btd_log_init(const char *debug, int detach) +{ + if (!init_logd() && !init_legacy_log()) + return; + + if (debug) { + struct btd_debug_desc *desc; + + for (desc = __start___debug; desc < __stop___debug; desc++) + desc->flags |= BTD_DEBUG_FLAG_PRINT; + } + + info("Bluetooth daemon %s", VERSION); +} + +void __btd_log_cleanup(void) +{ + if (log_fd < 0) + return; + + close(log_fd); + log_fd = -1; +}
diff --git a/android/main.c b/android/main.c new file mode 100644 index 0000000..03c8760 --- /dev/null +++ b/android/main.c
@@ -0,0 +1,805 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <sys/signalfd.h> +#if defined(ANDROID) +#include <sys/prctl.h> +#include <sys/capability.h> +#endif + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "src/log.h" +#include "src/sdpd.h" +#include "src/shared/util.h" + +#include "ipc-common.h" +#include "ipc.h" +#include "bluetooth.h" +#include "socket.h" +#include "hidhost.h" +#include "hal-msg.h" +#include "a2dp.h" +#include "pan.h" +#include "avrcp.h" +#include "handsfree.h" +#include "gatt.h" +#include "health.h" +#include "handsfree-client.h" +#include "map-client.h" +#include "utils.h" + +#define DEFAULT_VENDOR "BlueZ" +#define DEFAULT_MODEL "BlueZ for Android" +#define DEFAULT_NAME "BlueZ for Android" + +#define STARTUP_GRACE_SECONDS 5 +#define SHUTDOWN_GRACE_SECONDS 5 + +static char *config_vendor = NULL; +static char *config_model = NULL; +static char *config_name = NULL; +static char *config_serial = NULL; +static char *config_fw_rev = NULL; +static char *config_hw_rev = NULL; +static uint64_t config_system_id = 0; +static uint16_t config_pnp_source = 0x0002; /* USB */ +static uint16_t config_pnp_vendor = 0x1d6b; /* Linux Foundation */ +static uint16_t config_pnp_product = 0x0247; /* BlueZ for Android */ +static uint16_t config_pnp_version = 0x0000; + +static guint quit_timeout = 0; + +static bdaddr_t adapter_bdaddr; + +static GMainLoop *event_loop; + +static struct ipc *hal_ipc = NULL; + +static bool services[HAL_SERVICE_ID_MAX + 1] = { false }; + +const char *bt_config_get_vendor(void) +{ + if (config_vendor) + return config_vendor; + + return DEFAULT_VENDOR; +} + +const char *bt_config_get_name(void) +{ + if (config_name) + return config_name; + + return DEFAULT_NAME; +} + +const char *bt_config_get_model(void) +{ + if (config_model) + return config_model; + + return DEFAULT_MODEL; +} + +const char *bt_config_get_serial(void) +{ + return config_serial; +} + +const char *bt_config_get_fw_rev(void) +{ + return config_fw_rev; +} + +const char *bt_config_get_hw_rev(void) +{ + return config_hw_rev; +} + +uint64_t bt_config_get_system_id(void) +{ + return config_system_id; +} + +uint16_t bt_config_get_pnp_source(void) +{ + return config_pnp_source; +} + +uint16_t bt_config_get_pnp_vendor(void) +{ + return config_pnp_vendor; +} + +uint16_t bt_config_get_pnp_product(void) +{ + return config_pnp_product; +} + +uint16_t bt_config_get_pnp_version(void) +{ + return config_pnp_version; +} + +static void service_register(const void *buf, uint16_t len) +{ + const struct hal_cmd_register_module *m = buf; + uint8_t status; + + if (m->service_id > HAL_SERVICE_ID_MAX || services[m->service_id]) { + status = HAL_STATUS_FAILED; + goto failed; + } + + switch (m->service_id) { + case HAL_SERVICE_ID_BLUETOOTH: + if (!bt_bluetooth_register(hal_ipc, m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_SOCKET: + bt_socket_register(hal_ipc, &adapter_bdaddr, m->mode); + + break; + case HAL_SERVICE_ID_HIDHOST: + if (!bt_hid_register(hal_ipc, &adapter_bdaddr, m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_A2DP: + if (!bt_a2dp_register(hal_ipc, &adapter_bdaddr, m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_PAN: + if (!bt_pan_register(hal_ipc, &adapter_bdaddr, m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_AVRCP: + if (!bt_avrcp_register(hal_ipc, &adapter_bdaddr, m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_HANDSFREE: + if (!bt_handsfree_register(hal_ipc, &adapter_bdaddr, m->mode, + m->max_clients)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_GATT: + if (!bt_gatt_register(hal_ipc, &adapter_bdaddr)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_HEALTH: + if (!bt_health_register(hal_ipc, &adapter_bdaddr, m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_HANDSFREE_CLIENT: + if (!bt_hf_client_register(hal_ipc, &adapter_bdaddr)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + case HAL_SERVICE_ID_MAP_CLIENT: + if (!bt_map_client_register(hal_ipc, &adapter_bdaddr, + m->mode)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + break; + default: + DBG("service %u not supported", m->service_id); + status = HAL_STATUS_FAILED; + goto failed; + } + + services[m->service_id] = true; + + status = HAL_STATUS_SUCCESS; + + info("Service ID=%u registered", m->service_id); + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_CORE, HAL_OP_REGISTER_MODULE, + status); +} + +static bool unregister_service(uint8_t id) +{ + if (id > HAL_SERVICE_ID_MAX || !services[id]) + return false; + + switch (id) { + case HAL_SERVICE_ID_BLUETOOTH: + bt_bluetooth_unregister(); + break; + case HAL_SERVICE_ID_SOCKET: + bt_socket_unregister(); + break; + case HAL_SERVICE_ID_HIDHOST: + bt_hid_unregister(); + break; + case HAL_SERVICE_ID_A2DP: + bt_a2dp_unregister(); + break; + case HAL_SERVICE_ID_PAN: + bt_pan_unregister(); + break; + case HAL_SERVICE_ID_AVRCP: + bt_avrcp_unregister(); + break; + case HAL_SERVICE_ID_HANDSFREE: + bt_handsfree_unregister(); + break; + case HAL_SERVICE_ID_GATT: + bt_gatt_unregister(); + break; + case HAL_SERVICE_ID_HEALTH: + bt_health_unregister(); + break; + case HAL_SERVICE_ID_HANDSFREE_CLIENT: + bt_hf_client_unregister(); + break; + case HAL_SERVICE_ID_MAP_CLIENT: + bt_map_client_unregister(); + break; + default: + DBG("service %u not supported", id); + return false; + } + + services[id] = false; + + return true; +} + +static void service_unregister(const void *buf, uint16_t len) +{ + const struct hal_cmd_unregister_module *m = buf; + uint8_t status; + + if (!unregister_service(m->service_id)) { + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + + info("Service ID=%u unregistered", m->service_id); + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_CORE, HAL_OP_UNREGISTER_MODULE, + status); +} + +static char *get_prop(char *prop, uint16_t len, const uint8_t *val) +{ + /* TODO should fail if set more than once ? */ + free(prop); + + prop = malloc0(len); + if (!prop) + return NULL; + + memcpy(prop, val, len); + prop[len - 1] = '\0'; + + return prop; +} + +static void parse_pnp_id(uint16_t len, const uint8_t *val) +{ + int result; + uint16_t vendor, product, version , source; + char *pnp; + + /* version is optional */ + version = config_pnp_version; + + pnp = get_prop(NULL, len, val); + if (!pnp) + return; + + DBG("pnp_id %s", pnp); + + result = sscanf(pnp, "bluetooth:%4hx:%4hx:%4hx", + &vendor, &product, &version); + if (result != EOF && result >= 2) { + source = 0x0001; + goto done; + } + + result = sscanf(pnp, "usb:%4hx:%4hx:%4hx", &vendor, &product, &version); + if (result != EOF && result >= 2) { + source = 0x0002; + goto done; + } + + free(pnp); + return; +done: + free(pnp); + + config_pnp_source = source; + config_pnp_vendor = vendor; + config_pnp_product = product; + config_pnp_version = version; +} + +static void parse_system_id(uint16_t len, const uint8_t *val) +{ + uint64_t res; + char *id; + + id = get_prop(NULL, len, val); + if (!id) + return; + + res = strtoull(id, NULL, 16); + if (res == ULLONG_MAX && errno == ERANGE) + goto done; + + config_system_id = res; +done: + free(id); +} + +static void configuration(const void *buf, uint16_t len) +{ + const struct hal_cmd_configuration *cmd = buf; + const struct hal_config_prop *prop; + unsigned int i; + + buf += sizeof(*cmd); + len -= sizeof(*cmd); + + for (i = 0; i < cmd->num; i++) { + prop = buf; + + if (len < sizeof(*prop) || len < sizeof(*prop) + prop->len) { + error("Invalid configuration command, terminating"); + raise(SIGTERM); + return; + } + + switch (prop->type) { + case HAL_CONFIG_VENDOR: + config_vendor = get_prop(config_vendor, prop->len, + prop->val); + DBG("vendor %s", config_vendor); + break; + case HAL_CONFIG_NAME: + config_name = get_prop(config_name, prop->len, + prop->val); + DBG("name %s", config_name); + break; + case HAL_CONFIG_MODEL: + config_model = get_prop(config_model, prop->len, + prop->val); + DBG("model %s", config_model); + break; + case HAL_CONFIG_SERIAL_NUMBER: + config_serial = get_prop(config_serial, prop->len, + prop->val); + DBG("serial %s", config_serial); + break; + case HAL_CONFIG_SYSTEM_ID: + parse_system_id(prop->len, prop->val); + break; + case HAL_CONFIG_PNP_ID: + parse_pnp_id(prop->len, prop->val); + break; + case HAL_CONFIG_FW_REV: + config_fw_rev = get_prop(config_fw_rev, prop->len, + prop->val); + DBG("fw_rev %s", config_fw_rev); + break; + case HAL_CONFIG_HW_REV: + config_hw_rev = get_prop(config_hw_rev, prop->len, + prop->val); + DBG("hw_rev %s", config_hw_rev); + break; + default: + error("Invalid configuration option (%u), terminating", + prop->type); + raise(SIGTERM); + return; + } + + buf += sizeof(*prop) + prop->len; + len -= sizeof(*prop) + prop->len; + } + + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_CORE, HAL_OP_CONFIGURATION, + HAL_STATUS_SUCCESS); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_REGISTER_MODULE */ + { service_register, false, sizeof(struct hal_cmd_register_module) }, + /* HAL_OP_UNREGISTER_MODULE */ + { service_unregister, false, sizeof(struct hal_cmd_unregister_module) }, + /* HAL_OP_CONFIGURATION */ + { configuration, true, sizeof(struct hal_cmd_configuration) }, +}; + +static void bluetooth_stopped(void) +{ + g_main_loop_quit(event_loop); +} + +static gboolean quit_eventloop(gpointer user_data) +{ + g_main_loop_quit(event_loop); + + quit_timeout = 0; + + return FALSE; +} + +static void stop_bluetooth(void) +{ + static bool __stop = false; + + if (__stop) + return; + + __stop = true; + + if (!bt_bluetooth_stop(bluetooth_stopped)) { + g_main_loop_quit(event_loop); + return; + } + + quit_timeout = g_timeout_add_seconds(SHUTDOWN_GRACE_SECONDS, + quit_eventloop, NULL); +} + +static void ipc_disconnected(void *data) +{ + stop_bluetooth(); +} + +static void adapter_ready(int err, const bdaddr_t *addr) +{ + if (err < 0) { + error("Adapter initialization failed: %s", strerror(-err)); + exit(EXIT_FAILURE); + } + + bacpy(&adapter_bdaddr, addr); + + if (quit_timeout > 0) { + g_source_remove(quit_timeout); + quit_timeout = 0; + } + + info("Adapter initialized"); + + hal_ipc = ipc_init(BLUEZ_HAL_SK_PATH, sizeof(BLUEZ_HAL_SK_PATH), + HAL_SERVICE_ID_MAX, true, + ipc_disconnected, NULL); + if (!hal_ipc) { + error("Failed to initialize IPC"); + exit(EXIT_FAILURE); + } + + ipc_register(hal_ipc, HAL_SERVICE_ID_CORE, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + static bool __terminated = false; + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + return FALSE; + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + if (!__terminated) { + info("Terminating"); + stop_bluetooth(); + } + + __terminated = true; + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static gboolean option_version = FALSE; +static gint option_index = -1; +static gboolean option_dbg = FALSE; +static gboolean option_mgmt_dbg = FALSE; + +static GOptionEntry options[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit", NULL }, + { "index", 'i', 0, G_OPTION_ARG_INT, &option_index, + "Use specified controller", "INDEX"}, + { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_dbg, + "Enable debug logs", NULL}, + { "mgmt-debug", 0, 0, G_OPTION_ARG_NONE, &option_mgmt_dbg, + "Enable mgmt debug logs", NULL}, + + { NULL } +}; + +static void cleanup_services(void) +{ + int i; + + DBG(""); + + for (i = HAL_SERVICE_ID_MAX; i > HAL_SERVICE_ID_CORE; i--) + unregister_service(i); +} + +static bool set_capabilities(void) +{ +#if defined(ANDROID) + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; + + /* + * CAP_NET_ADMIN: Allow use of MGMT interface + * CAP_NET_BIND_SERVICE: Allow use of privileged PSM + * CAP_NET_RAW: Allow use of bnep ioctl calls + */ + cap.effective = cap.permitted = + CAP_TO_MASK(CAP_NET_RAW) | + CAP_TO_MASK(CAP_NET_ADMIN) | + CAP_TO_MASK(CAP_NET_BIND_SERVICE); + cap.inheritable = 0; + + /* don't clear capabilities when dropping root */ + if (prctl(PR_SET_KEEPCAPS, 1) < 0) { + error("%s: prctl(): %s", __func__, strerror(errno)); + return false; + } + + /* Android bluetooth user UID=1002 */ + if (setuid(1002) < 0) { + error("%s: setuid(): %s", __func__, strerror(errno)); + return false; + } + + /* TODO: Move to cap_set_proc once bionic support it */ + if (capset(&header, &cap) < 0) { + error("%s: capset(): %s", __func__, strerror(errno)); + return false; + } + + /* TODO: Move to cap_get_proc once bionic support it */ + if (capget(&header, &cap) < 0) { + error("%s: capget(): %s", __func__, strerror(errno)); + return false; + } + + DBG("Caps: eff: 0x%x, perm: 0x%x, inh: 0x%x", cap.effective, + cap.permitted, cap.inheritable); + +#endif + return true; +} + +static void set_version(void) +{ + uint8_t major, minor; + + if (sscanf(VERSION, "%hhu.%hhu", &major, &minor) != 2) + return; + + config_pnp_version = major << 8 | minor; +} + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GError *err = NULL; + guint signal; + + set_version(); + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { + if (err != NULL) { + g_printerr("%s\n", err->message); + g_error_free(err); + } else + g_printerr("An unknown error occurred\n"); + + exit(EXIT_FAILURE); + } + + g_option_context_free(context); + + if (option_version == TRUE) { + printf("%s\n", VERSION); + exit(EXIT_SUCCESS); + } + + signal = setup_signalfd(); + if (!signal) + return EXIT_FAILURE; + + if (option_dbg || option_mgmt_dbg) + __btd_log_init("*", 0); + else + __btd_log_init(NULL, 0); + + if (!set_capabilities()) { + __btd_log_cleanup(); + g_source_remove(signal); + return EXIT_FAILURE; + } + + quit_timeout = g_timeout_add_seconds(STARTUP_GRACE_SECONDS, + quit_eventloop, NULL); + if (quit_timeout == 0) { + error("Failed to init startup timeout"); + __btd_log_cleanup(); + g_source_remove(signal); + return EXIT_FAILURE; + } + + if (!bt_bluetooth_start(option_index, option_mgmt_dbg, adapter_ready)) { + __btd_log_cleanup(); + g_source_remove(quit_timeout); + g_source_remove(signal); + return EXIT_FAILURE; + } + + /* Use params: mtu = 0, flags = 0 */ + start_sdp_server(0, 0); + + DBG("Entering main loop"); + + event_loop = g_main_loop_new(NULL, FALSE); + + g_main_loop_run(event_loop); + + g_source_remove(signal); + + if (quit_timeout > 0) + g_source_remove(quit_timeout); + + cleanup_services(); + + stop_sdp_server(); + bt_bluetooth_cleanup(); + g_main_loop_unref(event_loop); + + /* If no adapter was initialized, hal_ipc is NULL */ + if (hal_ipc) { + ipc_unregister(hal_ipc, HAL_SERVICE_ID_CORE); + ipc_cleanup(hal_ipc); + } + + info("Exit"); + + __btd_log_cleanup(); + + free(config_vendor); + free(config_model); + free(config_name); + free(config_serial); + free(config_fw_rev); + free(config_hw_rev); + + return EXIT_SUCCESS; +}
diff --git a/android/map-client.c b/android/map-client.c new file mode 100644 index 0000000..e3ad148 --- /dev/null +++ b/android/map-client.c
@@ -0,0 +1,203 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <glib.h> + +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "src/sdp-client.h" + +#include "ipc.h" +#include "lib/bluetooth.h" +#include "map-client.h" +#include "src/log.h" +#include "hal-msg.h" +#include "ipc-common.h" +#include "utils.h" +#include "src/shared/util.h" + +static struct ipc *hal_ipc = NULL; +static bdaddr_t adapter_addr; + +static int fill_mce_inst(void *buf, int32_t id, int32_t scn, int32_t msg_type, + const void *name, uint8_t name_len) +{ + struct hal_map_client_mas_instance *inst = buf; + + inst->id = id; + inst->scn = scn; + inst->msg_types = msg_type; + inst->name_len = name_len; + + if (name_len) + memcpy(inst->name, name, name_len); + + return sizeof(*inst) + name_len; +} + +static void map_client_sdp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + uint8_t buf[IPC_MTU]; + struct hal_ev_map_client_remote_mas_instances *ev = (void *) buf; + bdaddr_t *dst = data; + sdp_list_t *list, *protos; + uint8_t status; + int32_t id, scn, msg_type, name_len, num_instances = 0; + char *name; + size_t size; + + size = sizeof(*ev); + bdaddr2android(dst, &ev->bdaddr); + + if (err < 0) { + error("mce: Unable to get SDP record: %s", strerror(-err)); + status = HAL_STATUS_FAILED; + goto fail; + } + + for (list = recs; list != NULL; list = list->next) { + sdp_record_t *rec = list->data; + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_MAS_INSTANCE_ID); + if (!data) { + error("mce: cannot get mas instance id"); + continue; + } + + id = data->val.uint8; + + data = sdp_data_get(rec, SDP_ATTR_SVCNAME_PRIMARY); + if (!data) { + error("mce: cannot get mas instance name"); + continue; + } + + name = data->val.str; + name_len = data->unitSize; + + data = sdp_data_get(rec, SDP_ATTR_SUPPORTED_MESSAGE_TYPES); + if (!data) { + error("mce: cannot get mas instance msg type"); + continue; + } + + msg_type = data->val.uint8; + + if (sdp_get_access_protos(rec, &protos) < 0) { + error("mce: cannot get mas instance sdp protocol list"); + continue; + } + + scn = 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 (!scn) { + error("mce: cannot get mas instance rfcomm channel"); + continue; + } + + size += fill_mce_inst(buf + size, id, scn, msg_type, name, + name_len); + num_instances++; + } + + status = HAL_STATUS_SUCCESS; + +fail: + ev->num_instances = num_instances; + ev->status = status; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_MAP_CLIENT, + HAL_EV_MAP_CLIENT_REMOTE_MAS_INSTANCES, size, buf); +} + +static void handle_get_instances(const void *buf, uint16_t len) +{ + const struct hal_cmd_map_client_get_instances *cmd = buf; + uint8_t status; + bdaddr_t *dst; + uuid_t uuid; + + DBG(""); + + dst = new0(bdaddr_t, 1); + if (!dst) { + error("mce: Fail to allocate cb data"); + status = HAL_STATUS_FAILED; + goto failed; + } + + android2bdaddr(&cmd->bdaddr, dst); + sdp_uuid16_create(&uuid, MAP_MSE_SVCLASS_ID); + + if (bt_search_service(&adapter_addr, dst, &uuid, + map_client_sdp_search_cb, dst, free, 0)) { + error("mce: Failed to search SDP details"); + status = HAL_STATUS_FAILED; + goto failed; + } + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_MAP_CLIENT, + HAL_OP_MAP_CLIENT_GET_INSTANCES, status); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_MAP_CLIENT_GET_INSTANCES */ + { handle_get_instances, false, + sizeof(struct hal_cmd_map_client_get_instances) }, +}; + +bool bt_map_client_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + DBG(""); + + bacpy(&adapter_addr, addr); + + hal_ipc = ipc; + + ipc_register(hal_ipc, HAL_SERVICE_ID_MAP_CLIENT, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; +} + +void bt_map_client_unregister(void) +{ + DBG(""); + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_MAP_CLIENT); + hal_ipc = NULL; +}
diff --git a/android/map-client.h b/android/map-client.h new file mode 100644 index 0000000..0e63072 --- /dev/null +++ b/android/map-client.h
@@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_map_client_register(struct ipc *ipc, const bdaddr_t *addr, + uint8_t mode); +void bt_map_client_unregister(void);
diff --git a/android/pan.c b/android/pan.c new file mode 100644 index 0000000..c40a6d3 --- /dev/null +++ b/android/pan.c
@@ -0,0 +1,903 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 <stdbool.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <glib.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <net/if.h> +#include <linux/sockios.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <linux/if_bridge.h> + +#include "btio/btio.h" +#include "lib/bluetooth.h" +#include "lib/bnep.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "src/uuid-helper.h" +#include "profiles/network/bnep.h" +#include "src/log.h" + +#include "hal-msg.h" +#include "ipc-common.h" +#include "ipc.h" +#include "utils.h" +#include "bluetooth.h" +#include "pan.h" + +#define SVC_HINT_NETWORKING 0x02 + +#define BNEP_BRIDGE "bt-pan" +#define BNEP_PANU_INTERFACE "bt-pan" +#define BNEP_NAP_INTERFACE "bt-pan%d" + +struct pan_device { + char iface[16]; + bdaddr_t dst; + uint8_t conn_state; + uint8_t role; + GIOChannel *io; + struct bnep *session; + guint watch; +}; + +static bdaddr_t adapter_addr; +static GSList *devices = NULL; +static uint8_t local_role = HAL_PAN_ROLE_NONE; +static uint32_t nap_rec_id = 0; +static uint32_t panu_rec_id = 0; +static GIOChannel *nap_io = NULL; +static bool nap_bridge_mode = false; +static struct ipc *hal_ipc = NULL; + +static int set_forward_delay(int sk) +{ + unsigned long args[4] = { BRCTL_SET_BRIDGE_FORWARD_DELAY, 0 , 0, 0 }; + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, BNEP_BRIDGE, IFNAMSIZ - 1); + ifr.ifr_data = (char *) args; + + if (ioctl(sk, SIOCDEVPRIVATE, &ifr) < 0) { + error("pan: setting forward delay failed: %d (%s)", + errno, strerror(errno)); + return -1; + } + + return 0; +} + +static int nap_create_bridge(void) +{ + int sk, err; + + DBG("%s", BNEP_BRIDGE); + + if (nap_bridge_mode) + return 0; + + sk = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sk < 0) + return -EOPNOTSUPP; + + if (ioctl(sk, SIOCBRADDBR, BNEP_BRIDGE) < 0) { + err = -errno; + if (err != -EEXIST) { + close(sk); + return -EOPNOTSUPP; + } + } + + err = set_forward_delay(sk); + if (err < 0) + ioctl(sk, SIOCBRDELBR, BNEP_BRIDGE); + + close(sk); + + nap_bridge_mode = err == 0; + + return err; +} + +static int bridge_if_down(void) +{ + struct ifreq ifr; + int sk, err; + + sk = socket(AF_INET, SOCK_DGRAM, 0); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, BNEP_BRIDGE, IF_NAMESIZE - 1); + + ifr.ifr_flags &= ~IFF_UP; + + /* Bring down the interface */ + err = ioctl(sk, SIOCSIFFLAGS, (caddr_t) &ifr); + + close(sk); + + if (err < 0) { + error("pan: Could not bring down %s", BNEP_BRIDGE); + return err; + } + + return 0; +} + +static int nap_remove_bridge(void) +{ + int sk, err; + + DBG("%s", BNEP_BRIDGE); + + if (!nap_bridge_mode) + return 0; + + bridge_if_down(); + + sk = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sk < 0) + return -EOPNOTSUPP; + + err = ioctl(sk, SIOCBRDELBR, BNEP_BRIDGE); + if (err < 0) + err = -errno; + + close(sk); + + if (err < 0) + return err; + + nap_bridge_mode = false; + + return 0; +} + +static int device_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct pan_device *dev = s; + const bdaddr_t *dst = user_data; + + return bacmp(&dev->dst, dst); +} + +static void pan_device_free(void *data) +{ + struct pan_device *dev = data; + + if (dev->watch > 0) { + bnep_server_delete(BNEP_BRIDGE, dev->iface, &dev->dst); + g_source_remove(dev->watch); + } + + if (dev->io) { + g_io_channel_shutdown(dev->io, FALSE, NULL); + g_io_channel_unref(dev->io); + } + + if (dev->session) + bnep_free(dev->session); + + g_free(dev); +} + +static void pan_device_remove(struct pan_device *dev) +{ + devices = g_slist_remove(devices, dev); + + if (g_slist_length(devices) == 0) { + local_role = HAL_PAN_ROLE_NONE; + nap_remove_bridge(); + } + + pan_device_free(dev); +} + +static void bt_pan_notify_conn_state(struct pan_device *dev, uint8_t state) +{ + struct hal_ev_pan_conn_state ev; + char addr[18]; + + if (dev->conn_state == state) + return; + + dev->conn_state = state; + ba2str(&dev->dst, addr); + DBG("device %s state %u", addr, state); + + bdaddr2android(&dev->dst, ev.bdaddr); + ev.state = state; + ev.local_role = local_role; + ev.remote_role = dev->role; + ev.status = HAL_STATUS_SUCCESS; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_PAN, HAL_EV_PAN_CONN_STATE, + sizeof(ev), &ev); + if (dev->conn_state == HAL_PAN_STATE_DISCONNECTED) + pan_device_remove(dev); +} + +static void bt_pan_notify_ctrl_state(struct pan_device *dev, uint8_t state, + uint8_t status) +{ + struct hal_ev_pan_ctrl_state ev; + + DBG(""); + + ev.state = state; + ev.local_role = local_role; + ev.status = status; + + memset(ev.name, 0, sizeof(ev.name)); + + if (local_role == HAL_PAN_ROLE_NAP) + memcpy(ev.name, BNEP_BRIDGE, sizeof(BNEP_BRIDGE)); + else if (local_role == HAL_PAN_ROLE_PANU) + memcpy(ev.name, dev->iface, sizeof(dev->iface)); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_PAN, HAL_EV_PAN_CTRL_STATE, + sizeof(ev), &ev); +} + +static void bnep_disconn_cb(void *data) +{ + struct pan_device *dev = data; + + DBG("%s disconnected", dev->iface); + + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); +} + +static void bnep_conn_cb(char *iface, int err, void *data) +{ + struct pan_device *dev = data; + + DBG(""); + + if (err < 0) { + error("bnep connect req failed: %s", strerror(-err)); + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); + return; + } + + memcpy(dev->iface, iface, sizeof(dev->iface)); + + DBG("%s connected", dev->iface); + + bt_pan_notify_ctrl_state(dev, HAL_PAN_CTRL_ENABLED, HAL_STATUS_SUCCESS); + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_CONNECTED); +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer data) +{ + struct pan_device *dev = data; + uint16_t l_role, r_role; + int perr, sk; + + DBG(""); + + if (err) { + error("%s", err->message); + goto fail; + } + + l_role = (local_role == HAL_PAN_ROLE_NAP) ? BNEP_SVC_NAP : + BNEP_SVC_PANU; + r_role = (dev->role == HAL_PAN_ROLE_NAP) ? BNEP_SVC_NAP : BNEP_SVC_PANU; + + sk = g_io_channel_unix_get_fd(dev->io); + + dev->session = bnep_new(sk, l_role, r_role, BNEP_PANU_INTERFACE); + if (!dev->session) + goto fail; + + perr = bnep_connect(dev->session, bnep_conn_cb, bnep_disconn_cb, dev, + dev); + if (perr < 0) { + error("bnep connect req failed: %s", strerror(-perr)); + goto fail; + } + + if (dev->io) { + g_io_channel_unref(dev->io); + dev->io = NULL; + } + + return; + +fail: + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); +} + +static void bt_pan_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_pan_connect *cmd = buf; + struct pan_device *dev; + uint8_t status; + bdaddr_t dst; + char addr[18]; + GSList *l; + GError *gerr = NULL; + + DBG(""); + + switch (cmd->local_role) { + case HAL_PAN_ROLE_NAP: + if (cmd->remote_role != HAL_PAN_ROLE_PANU) { + status = HAL_STATUS_UNSUPPORTED; + goto failed; + } + break; + case HAL_PAN_ROLE_PANU: + if (cmd->remote_role != HAL_PAN_ROLE_NAP && + cmd->remote_role != HAL_PAN_ROLE_PANU) { + status = HAL_STATUS_UNSUPPORTED; + goto failed; + } + break; + default: + status = HAL_STATUS_UNSUPPORTED; + goto failed; + } + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = g_new0(struct pan_device, 1); + bacpy(&dev->dst, &dst); + local_role = cmd->local_role; + dev->role = cmd->remote_role; + + ba2str(&dev->dst, addr); + DBG("connecting to %s %s", addr, dev->iface); + + dev->io = bt_io_connect(connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, BNEP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_OMTU, BNEP_MTU, + BT_IO_OPT_IMTU, BNEP_MTU, + BT_IO_OPT_INVALID); + if (!dev->io) { + error("%s", gerr->message); + g_error_free(gerr); + g_free(dev); + status = HAL_STATUS_FAILED; + goto failed; + } + + devices = g_slist_append(devices, dev); + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_CONNECTING); + + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_PAN, HAL_OP_PAN_CONNECT, status); +} + +static void bt_pan_disconnect(const void *buf, uint16_t len) +{ + const struct hal_cmd_pan_disconnect *cmd = buf; + struct pan_device *dev; + uint8_t status; + GSList *l; + bdaddr_t dst; + + DBG(""); + + android2bdaddr(&cmd->bdaddr, &dst); + + l = g_slist_find_custom(devices, &dst, device_cmp); + if (!l) { + status = HAL_STATUS_FAILED; + goto failed; + } + + dev = l->data; + + if (dev->conn_state == HAL_PAN_STATE_CONNECTED && dev->session) + bnep_disconnect(dev->session); + + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); + status = HAL_STATUS_SUCCESS; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_PAN, HAL_OP_PAN_DISCONNECT, + status); +} + +static gboolean nap_watchdog_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct pan_device *dev = user_data; + + DBG("disconnected"); + + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); + + return FALSE; +} + +static gboolean nap_setup_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct pan_device *dev = user_data; + uint8_t packet[BNEP_MTU]; + int sk, n, err; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + error("Hangup or error or inval on BNEP socket"); + return FALSE; + } + + sk = g_io_channel_unix_get_fd(chan); + + /* + * BNEP_SETUP_CONNECTION_REQUEST_MSG should be read and left in case + * of kernel setup connection msg handling. + */ + n = recv(sk, packet, sizeof(packet), MSG_PEEK); + if (n < 0) { + error("read(): %s(%d)", strerror(errno), errno); + goto failed; + } + + if (n < 3) { + error("pan: to few setup connection request data received"); + goto failed; + } + + err = nap_create_bridge(); + if (err < 0) + error("pan: Failed to create bridge: %s (%d)", strerror(-err), + -err); + + if (bnep_server_add(sk, (err < 0) ? NULL : BNEP_BRIDGE, dev->iface, + &dev->dst, packet, n) < 0) { + error("pan: server_connadd failed"); + goto failed; + } + + dev->watch = g_io_add_watch(chan, G_IO_HUP | G_IO_ERR | G_IO_NVAL, + nap_watchdog_cb, dev); + g_io_channel_unref(dev->io); + dev->io = NULL; + + bt_pan_notify_ctrl_state(dev, HAL_PAN_CTRL_ENABLED, HAL_STATUS_SUCCESS); + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_CONNECTED); + + return FALSE; + +failed: + pan_device_remove(dev); + + return FALSE; +} + +static void nap_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct pan_device *dev = user_data; + + DBG(""); + + if (err) { + error("%s", err->message); + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); + return; + } + + g_io_channel_set_close_on_unref(chan, TRUE); + dev->watch = g_io_add_watch(chan, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + nap_setup_cb, dev); +} + +static void nap_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct pan_device *dev; + bdaddr_t dst; + char address[18]; + GError *err = NULL; + + DBG(""); + + bt_io_get(chan, &err, BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + return; + } + + DBG("incoming connect request from %s", address); + dev = g_new0(struct pan_device, 1); + bacpy(&dev->dst, &dst); + local_role = HAL_PAN_ROLE_NAP; + dev->role = HAL_PAN_ROLE_PANU; + + strncpy(dev->iface, BNEP_NAP_INTERFACE, 16); + dev->iface[15] = '\0'; + + dev->io = g_io_channel_ref(chan); + g_io_channel_set_close_on_unref(dev->io, TRUE); + + if (!bt_io_accept(dev->io, nap_connect_cb, dev, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + goto failed; + } + + devices = g_slist_append(devices, dev); + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_CONNECTING); + + return; + +failed: + bt_pan_notify_conn_state(dev, HAL_PAN_STATE_DISCONNECTED); +} + +static void destroy_nap_device(void) +{ + DBG(""); + + nap_remove_bridge(); + + if (nap_io) { + g_io_channel_shutdown(nap_io, FALSE, NULL); + g_io_channel_unref(nap_io); + nap_io = NULL; + } +} + +static int register_nap_server(void) +{ + GError *gerr = NULL; + + DBG(""); + + nap_io = bt_io_listen(NULL, nap_confirm_cb, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_PSM, BNEP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_OMTU, BNEP_MTU, + BT_IO_OPT_IMTU, BNEP_MTU, + BT_IO_OPT_INVALID); + + if (!nap_io) { + destroy_nap_device(); + error("%s", gerr->message); + g_error_free(gerr); + return -EIO; + } + + return 0; +} + +static void bt_pan_enable(const void *buf, uint16_t len) +{ + const struct hal_cmd_pan_enable *cmd = buf; + uint8_t status, state; + int err; + + DBG(""); + + if (local_role == cmd->local_role) { + status = HAL_STATUS_SUCCESS; + goto reply; + } + + /* destroy existing server */ + destroy_nap_device(); + + switch (cmd->local_role) { + case HAL_PAN_ROLE_NAP: + break; + case HAL_PAN_ROLE_NONE: + local_role = HAL_PAN_ROLE_NONE; + status = HAL_STATUS_SUCCESS; + state = HAL_PAN_CTRL_DISABLED; + goto notify; + default: + status = HAL_STATUS_UNSUPPORTED; + goto reply; + } + + local_role = cmd->local_role; + err = register_nap_server(); + if (err < 0) { + status = HAL_STATUS_FAILED; + destroy_nap_device(); + goto reply; + } + + status = HAL_STATUS_SUCCESS; + state = HAL_PAN_CTRL_ENABLED; + +notify: + bt_pan_notify_ctrl_state(NULL, state, status); + +reply: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_PAN, HAL_OP_PAN_ENABLE, status); +} + +static void bt_pan_get_role(const void *buf, uint16_t len) +{ + struct hal_rsp_pan_get_role rsp; + + DBG(""); + + rsp.local_role = local_role; + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_PAN, HAL_OP_PAN_GET_ROLE, + sizeof(rsp), &rsp, -1); +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_PAN_ENABLE */ + { bt_pan_enable, false, sizeof(struct hal_cmd_pan_enable) }, + /* HAL_OP_PAN_GET_ROLE */ + { bt_pan_get_role, false, 0 }, + /* HAL_OP_PAN_CONNECT */ + { bt_pan_connect, false, sizeof(struct hal_cmd_pan_connect) }, + /* HAL_OP_PAN_DISCONNECT */ + { bt_pan_disconnect, false, sizeof(struct hal_cmd_pan_disconnect) }, +}; + +static sdp_record_t *nap_record(void) +{ + sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; + uuid_t root_uuid, nap, 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 = 0x0001, type = 0xfffe; + uint32_t rate = 0; + const char *desc = "Network Access Point", *name = "Network Service"; + sdp_record_t *record; + uint16_t ptype[] = { 0x0800, /* IPv4 */ 0x0806, /* ARP */ }; + sdp_data_t *head, *pseq, *data; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + record->attrlist = NULL; + record->pattern = NULL; + + sdp_uuid16_create(&nap, NAP_SVCLASS_ID); + svclass = sdp_list_append(NULL, &nap); + 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, &type); + sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, + SDP_UINT32, &rate); + + 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); + + head = sdp_data_alloc(SDP_UINT16, &ptype[0]); + data = sdp_data_alloc(SDP_UINT16, &ptype[1]); + 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(NULL, apseq); + sdp_set_access_protos(record, aproto); + sdp_add_lang_attr(record); + sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, SDP_UINT16, &security); + + 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 sdp_record_t *panu_record(void) +{ + sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; + uuid_t root_uuid, panu, 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 = 0x0001, type = 0xfffe; + uint32_t rate = 0; + const char *desc = "PAN User", *name = "Network Service"; + sdp_record_t *record; + uint16_t ptype[] = { 0x0800, /* IPv4 */ 0x0806, /* ARP */ }; + sdp_data_t *head, *pseq, *data; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + record->attrlist = NULL; + record->pattern = NULL; + + sdp_uuid16_create(&panu, PANU_SVCLASS_ID); + svclass = sdp_list_append(NULL, &panu); + 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); + sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE, SDP_UINT16, &type); + sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, + SDP_UINT32, &rate); + + 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); + + head = sdp_data_alloc(SDP_UINT16, &ptype[0]); + data = sdp_data_alloc(SDP_UINT16, &ptype[1]); + 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(NULL, apseq); + sdp_set_access_protos(record, aproto); + sdp_add_lang_attr(record); + sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, SDP_UINT16, &security); + + 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; +} + +bool bt_pan_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + sdp_record_t *nap_rec, *panu_rec; + int err; + + DBG(""); + + bacpy(&adapter_addr, addr); + + nap_rec = nap_record(); + if (bt_adapter_add_record(nap_rec, SVC_HINT_NETWORKING) < 0) { + sdp_record_free(nap_rec); + error("Failed to allocate PAN-NAP sdp record"); + return false; + } + + panu_rec = panu_record(); + if (bt_adapter_add_record(panu_rec, SVC_HINT_NETWORKING) < 0) { + sdp_record_free(nap_rec); + sdp_record_free(panu_rec); + error("Failed to allocate PAN-PANU sdp record"); + return false; + } + + err = bnep_init(); + if (err < 0) { + error("Failed to init BNEP"); + bt_adapter_remove_record(nap_rec->handle); + bt_adapter_remove_record(panu_rec->handle); + return false; + } + + err = register_nap_server(); + if (err < 0) { + error("Failed to register NAP server"); + bt_adapter_remove_record(nap_rec->handle); + bt_adapter_remove_record(panu_rec->handle); + bnep_cleanup(); + return false; + } + + nap_rec_id = nap_rec->handle; + panu_rec_id = panu_rec->handle; + + hal_ipc = ipc; + ipc_register(hal_ipc, HAL_SERVICE_ID_PAN, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); + + return true; +} + +void bt_pan_unregister(void) +{ + DBG(""); + + g_slist_free_full(devices, pan_device_free); + devices = NULL; + local_role = HAL_PAN_ROLE_NONE; + + bnep_cleanup(); + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_PAN); + hal_ipc = NULL; + + bt_adapter_remove_record(nap_rec_id); + nap_rec_id = 0; + bt_adapter_remove_record(panu_rec_id); + panu_rec_id = 0; + destroy_nap_device(); +}
diff --git a/android/pan.h b/android/pan.h new file mode 100644 index 0000000..cfbea96 --- /dev/null +++ b/android/pan.h
@@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * 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 + * + */ + +bool bt_pan_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_pan_unregister(void);
diff --git a/android/pics-a2dp.txt b/android/pics-a2dp.txt new file mode 100644 index 0000000..2e87104 --- /dev/null +++ b/android/pics-a2dp.txt
@@ -0,0 +1,162 @@ +A2DP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory if such role selected +O - optional + + Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_0_1 False A2DP 1.0 (C.1) +TSPC_A2DP_0_2 False A2DP 1.2 (C.1) +TSPC_A2DP_0_3 True (*) A2DP 1.3 (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to select one of the profile versions. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_1_1 True (*) Role: Source (C.1) +TSPC_A2DP_1_2 False Role: Sink (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + A2DP SRC Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_2_1 True SRC: Initiate connection establishment (M) +TSPC_A2DP_2_2 True SRC: Accept connection establishment (M) +TSPC_A2DP_2_3 True SRC: Initiate start streaming (M) +TSPC_A2DP_2_4 True SRC: Accept start streaming (M) +TSPC_A2DP_2_5 True SRC: Send audio stream (M) +TSPC_A2DP_2_6 True SRC: Initiate connection release (M) +TSPC_A2DP_2_7 True SRC: Accept connection release (M) +TSPC_A2DP_2_8 True (*) SRC: Initiate suspend (O) +TSPC_A2DP_2_9 True (*) SRC: Accept suspend (O) +TSPC_A2DP_2_10 True SRC: SBC encoder (M) +TSPC_A2DP_2_10a False SRC: Encode and Forward Audio Stream (O) +TSPC_A2DP_2_11 False SRC: SBC Configurations in 16 KHz sampling (O) +TSPC_A2DP_2_12 False SRC: SBC Configurations in 32 KHz sampling (O) +TSPC_A2DP_2_13 True (*) SRC: SBC Configurations in 44.1 KHz sampling + (C.1) +TSPC_A2DP_2_14 True (*) SRC: SBC Configurations in 48 KHz sampling (C.1) +TSPC_A2DP_2_15 False SRC: Delay Reporting (C.2) +TSPC_A2DP_2_16 False SRC: SRC video playback via Bluetooth VDP (C.3) +TSPC_A2DP_2_17 False SRC: SRC video playback on a local video + display (C.3) +------------------------------------------------------------------------------- +C.1: At least one of the values shall be supported. +C.2: Mandatory if A2DP 0/3 AND (2/16 OR 2/17) is supported, otherwise excluded. +C.3: Optional to support if A2DP 0/3 is supported, otherwise excluded. +------------------------------------------------------------------------------- + + + Supported Codecs in SRC +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_3_1 True SRC: SBC encoder (M) +TSPC_A2DP_3_1a False SRC: Encode and Forward SBC Audio Stream (O) +TSPC_A2DP_3_2 False SRC: Optional codec (O) +TSPC_A2DP_3_3 False SRC: MPEG-1,2 Audio decoder (C.1) +TSPC_A2DP_3_4 False SRC: MPEG-1,2 Audio encoder (C.1) +TSPC_A2DP_3_5 False SRC: MPEG-2,4 AAC decoder (C.1) +TSPC_A2DP_3_6 False SRC: MPEG-2,4 AAC encoder (C.1) +TSPC_A2DP_3_7 False SRC: ATRAC family decoder (C.1) +TSPC_A2DP_3_8 False SRC: ATRAC family encoder (C.1) +------------------------------------------------------------------------------- +C.1: At least one of the implementations shall be supported if 3/2 + is supported, else excluded. +------------------------------------------------------------------------------- + + + Supported Codec Features in SRC +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_3a_1 True SRC: Channel Mode - Mono (M) +TSPC_A2DP_3a_2 True (*) SRC: Channel Mode - Dual Channel (C.1) +TSPC_A2DP_3a_3 True (*) SRC: Channel Mode - Stereo (C.1) +TSPC_A2DP_3a_4 True (*) SRC: Channel Mode - Joint Stereo (C.1) +TSPC_A2DP_3a_5 True SRC: Block Length 4 (M) +TSPC_A2DP_3a_6 True SRC: Block Length 8 (M) +TSPC_A2DP_3a_7 True SRC: Block Length 12 (M) +TSPC_A2DP_3a_8 True SRC: Block Length 16 (M) +TSPC_A2DP_3a_9 True (*) SRC: Subbands - 4 (O) +TSPC_A2DP_3a_10 True SRC: Subbands - 8 (M) +TSPC_A2DP_3a_11 True (*) SRC: Allocation - SNR (O) +TSPC_A2DP_3a_12 True SRC: Allocation - Loudness (M) +------------------------------------------------------------------------------- +C.1: At least one of the values shall be supported. +------------------------------------------------------------------------------- + + + A2DP Sink Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_4_1 False SNK: Initiate connection establishment (O) +TSPC_A2DP_4_2 False (*) SNK: Accept connection establishment (M) +TSPC_A2DP_4_3 False SNK: Initiate start streaming (O) +TSPC_A2DP_4_4 False (*) SNK: Accept start streaming (M) +TSPC_A2DP_4_5 False (*) SNK: Receive audio stream (M) +TSPC_A2DP_4_6 False SNK: Initiate connection release (O) +TSPC_A2DP_4_7 False (*) SNK: Accept connection release (M) +TSPC_A2DP_4_8 False SNK: Initiate suspend (O) +TSPC_A2DP_4_9 False SNK: Accept suspend (O) +TSPC_A2DP_4_10 False (*) SNK: SBC decoder (M) +TSPC_A2DP_4_10a False SNK: Decode and Forward Audio Stream (O) +TSPC_A2DP_4_11 False SNK: SBC Configurations in 16 KHz sampling (O) +TSPC_A2DP_4_12 False SNK: SBC Configurations in 32 KHz sampling (O) +TSPC_A2DP_4_13 False (*) SNK: SBC Configurations in 44.1 KHz sampling (M) +TSPC_A2DP_4_14 False (*) SNK: SBC Configurations in 48 KHz sampling (M) +TSPC_A2DP_4_15 False SNK: Delay Reporting (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support if A2DP 0/3 is supported, otherwise excluded. +------------------------------------------------------------------------------- + + + Supported codecs in SNK +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_5_1 False (*) SNK: SBC decoder (M) +TSPC_A2DP_5_1a False SNK: Decode and Forward SBC Audio Stream (O) +TSPC_A2DP_5_2 False SNK: Optional codec decoder (O) +TSPC_A2DP_5_3 False SNK: MPEG-1,2 Audio (C.1) +TSPC_A2DP_5_4 False SNK: MPEG-2,4 AAC (C.1) +TSPC_A2DP_5_5 False SNK: ATRAC family (C.1) +------------------------------------------------------------------------------- +C.1: At least one codec shall be supported if Table 5/2 is supported, otherwise + excluded. +------------------------------------------------------------------------------- + + + Supported Codec Features in SNK +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_A2DP_5a_1 False (*) SNK: Channel Mode - Mono (M) +TSPC_A2DP_5a_2 False (*) SNK: Channel Mode - Dual Channel (M) +TSPC_A2DP_5a_3 False (*) SNK: Channel Mode - Stereo (M) +TSPC_A2DP_5a_4 False (*) SNK: Channel Mode - Joint Stereo (M) +TSPC_A2DP_5a_5 False (*) SNK: Block Length 4 (M) +TSPC_A2DP_5a_6 False (*) SNK: Block Length 8 (M) +TSPC_A2DP_5a_7 False (*) SNK: Block Length 12 (M) +TSPC_A2DP_5a_8 False (*) SNK: Block Length 16 (M) +TSPC_A2DP_5a_9 False (*) SNK: Subbands - 4 (M) +TSPC_A2DP_5a_10 False (*) SNK: Subbands - 8 (M) +TSPC_A2DP_5a_11 False (*) SNK: Allocation - SNR (M) +TSPC_A2DP_5a_12 False (*) SNK: Allocation - Loudness (M) +-------------------------------------------------------------------------------
diff --git a/android/pics-avctp.txt b/android/pics-avctp.txt new file mode 100644 index 0000000..3447962 --- /dev/null +++ b/android/pics-avctp.txt
@@ -0,0 +1,75 @@ +AVCTP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory if such role selected +O - optional + + Protocol Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVCTP_0_1 False AVCTP 1.0 (C.1) +TSPC_AVCTP_0_2 False AVCTP 1.2 (C.1) +TSPC_AVCTP_0_3 False AVCTP 1.3 (C.1) +TSPC_AVCTP_0_4 True (*) AVCTP 1.4 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support only one Protocol Version. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVCTP_1_1 True (*) Controller (C.1) +TSPC_AVCTP_1_2 True (*) Target (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Controller Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVCTP_2_1 False Message fragmentation (O) +TSPC_AVCTP_2_2 True Transaction label management (M) +TSPC_AVCTP_2_3 True Packet type field management (M) +TSPC_AVCTP_2_4 True Message type field management (M) +TSPC_AVCTP_2_5 True PID field management (M) +TSPC_AVCTP_2_6 True IPID field mangement (M) +TSPC_AVCTP_2_7 True Message information management (M) +TSPC_AVCTP_2_8 False Event registration for message reception (O) +TSPC_AVCTP_2_9 False Event registration for connection request (O) +TSPC_AVCTP_2_10 False Event registration for disconnection (O) +TSPC_AVCTP_2_11 False Connect request (O) +TSPC_AVCTP_2_12 False Disconnect request (O) +TSPC_AVCTP_2_13 False Send message (O) +TSPC_AVCTP_2_14 False Support for multiple AVCTP channel establishment + (O) +------------------------------------------------------------------------------- + + + Target Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVCTP_3_1 False Message fragmentation (O) +TSPC_AVCTP_3_2 True Transaction label management (M) +TSPC_AVCTP_3_3 True Packet type field management (M) +TSPC_AVCTP_3_4 True Message type field management (M) +TSPC_AVCTP_3_5 True PID field management (M) +TSPC_AVCTP_3_6 True IPID field management (M) +TSPC_AVCTP_3_7 True Message information management (M) +TSPC_AVCTP_3_8 True (*) Event registration for message reception (O) +TSPC_AVCTP_3_9 True (*) Event registration for connection request (O) +TSPC_AVCTP_3_10 True (*) Event registration for disconnection request (O) +TSPC_AVCTP_3_11 True (*) Connect request (O) +TSPC_AVCTP_3_12 True (*) Disconnect request (O) +TSPC_AVCTP_3_13 True (*) Send message (O) +TSPC_AVCTP_ALL False Enables all test cases when set to TRUE +-------------------------------------------------------------------------------
diff --git a/android/pics-avdtp.txt b/android/pics-avdtp.txt new file mode 100644 index 0000000..c0c5529 --- /dev/null +++ b/android/pics-avdtp.txt
@@ -0,0 +1,236 @@ +AVDTP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory if such role selected +O - optional + + Versions +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_0_1 False AVDTP 1.0 (C.1) +TSPC_AVDTP_0_2 False AVDTP 1.2 (C.1) +TSPC_AVDTP_0_3 True (*) AVDTP 1.3 (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to select only one of the protocol versions. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_1_1 True (*) Source (C.1) +TSPC_AVDTP_1_2 True (*) Sink (C.1) +TSPC_AVDTP_1_3 True (*) Initiator (C.2) +TSPC_AVDTP_1_4 True (*) Acceptor (C.2) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined roles. +C.2: It is within the scope of profiles using the AVDTP specification to + mandate Initiator/Acceptor capabilities. It is mandatory to support at + least one of the defined roles. +------------------------------------------------------------------------------- + + + Signaling Message Format (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_2_1 True Transaction label (M) +TSPC_AVDTP_2_2 True Packet type (M) +TSPC_AVDTP_2_3 True Message type (M) +TSPC_AVDTP_2_4 True Signal identifier (M) +------------------------------------------------------------------------------- + + + Signaling Channel Establishment/Disconnection (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_3_1 True (*) Establish signaling channel (O) +TSPC_AVDTP_3_2 True (*) Disconnect signaling channel (O) +------------------------------------------------------------------------------- + + + Stream Discovery and Configuration (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_4_1 True (*) Stream discover command (O) +TSPC_AVDTP_4_2 True (*) Stream get capabilities command (C.2) +TSPC_AVDTP_4_3 True (*) Set configuration command (O) +TSPC_AVDTP_4_4 True (*) Get configuration command (O) +TSPC_AVDTP_4_5 False Reconfigure command (O) +TSPC_AVDTP_4_6 True (*) Stream get all capabilities command (C.1) +------------------------------------------------------------------------------- +C.1: It is optional to support if TSPC_AVDTP_0_3 is supported, otherwise + excluded. +C.2: Mandatory to support if TSPC_AVDTP_4_6 is supported, otherwise Optional. +------------------------------------------------------------------------------- + + + Stream Establishment, Suspension and Release (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_5_1 True (*) Open stream command (O) +TSPC_AVDTP_5_2 True (*) Start stream command (O) +TSPC_AVDTP_5_3 True (*) Close stream command (O) +TSPC_AVDTP_5_4 True (*) Suspend command (O) +TSPC_AVDTP_5_5 True (*) Abort stream command (O) +------------------------------------------------------------------------------- + + + Security Signaling (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_6_1 False Content security control command (O) +------------------------------------------------------------------------------- + + + Message Fragmentation (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_7_1 True Signaling message fragmentation (M) +------------------------------------------------------------------------------- + + + Signaling Message Format (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_8_1 True Transaction label (M) +TSPC_AVDTP_8_2 True Packet type (M) +TSPC_AVDTP_8_3 True Message type (M) +TSPC_AVDTP_8_4 True Signal identifier (M) +------------------------------------------------------------------------------- + + + Signaling Channel Establishment/Disconnection (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_9_1 True (*) Establish signaling channel (O) +TSPC_AVDTP_9_2 True (*) Disconnect signaling channel (O) +------------------------------------------------------------------------------- + + + Stream Discovery and Configuration (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_10_1 True (*) Stream discover response (O) +TSPC_AVDTP_10_2 True (*) Stream get capabilities response (C.2) +TSPC_AVDTP_10_3 True (*) Set configuration response (O) +TSPC_AVDTP_10_4 True (*) Get configuration response (O) +TSPC_AVDTP_10_5 False Reconfigure response (O) +TSPC_AVDTP_10_6 True (*) Stream get all capabilities response (C.1) +------------------------------------------------------------------------------- +C.1: It is optional to support if TSPC_AVDTP_0_3 is supported, otherwise + excluded. +C.2: It is Mandatory to support if TSPC_AVDTP_10_6 is supported, otherwise + Optional. +------------------------------------------------------------------------------- + + + Stream Establishment, Suspension and Release (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_11_1 True (*) Open stream response (O) +TSPC_AVDTP_11_2 True (*) Start stream response (O) +TSPC_AVDTP_11_3 True (*) Close stream response (O) +TSPC_AVDTP_11_4 True (*) Suspend response (O) +TSPC_AVDTP_11_5 True (*) Abort stream response (O) +TSPC_AVDTP_11_6 True (*) General reject message (O) +------------------------------------------------------------------------------- + + + Security Signaling (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_12_1 False Content security control response (O) +------------------------------------------------------------------------------- + + + Message Fragmentation (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_13_1 True Signaling message fragmentation (M) +------------------------------------------------------------------------------- + + + Source Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_14_1 True Basic transport service support (M) +TSPC_AVDTP_14_2 False Reporting service support (O) +TSPC_AVDTP_14_3 False Recovery service support (O) +TSPC_AVDTP_14_4 False Multiplexing service support (O) +TSPC_AVDTP_14_5 False Robust header compression service support (O) +TSPC_AVDTP_14_6 True (*) Delay Reporting (C.1) +------------------------------------------------------------------------------- +C.1: It is optional to support if TSPC_AVDTP_0_3 is supported, else excluded. +------------------------------------------------------------------------------- + + + Sink Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_15_1 True Basic transport service support (M) +TSPC_AVDTP_15_2 False Reporting service support (O) +TSPC_AVDTP_15_3 False Recovery service support (O) +TSPC_AVDTP_15_4 False Multiplexing service support (O) +TSPC_AVDTP_15_5 False Robust header compression service support (O) +TSPC_AVDTP_15_6 True (*) Delay Reporting (C.1) +------------------------------------------------------------------------------- +C.1: It is optional to support if TSPC_AVDTP_0_3 is supported, else excluded. +------------------------------------------------------------------------------- + + + Message Error Handling Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_16_1 False Reporting Capability Error (C.1) +TSPC_AVDTP_16_2 False Reject Corrupted Messages (C.2) +TSPC_AVDTP_16_3 True (*) General Reject Response Includes Signal ID (C.3) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_AVDTP_0_2 or TSPC_AVDTP_0_3 supported, excluded + otherwise. +C.2: Optional, excluded if TSPC_AVDTP_16_3 (General Reject Response Includes + Signal ID) is supported. +C.3: Mandatory if TSPC_AVDTP_0_3 supported, otherwise Optional. +------------------------------------------------------------------------------- + + + Upper Test Interface +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_17_1 False Upper Test Interface provided (O) +------------------------------------------------------------------------------- + + + L2CAP Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVDTP_18_1 False Enhanced Retransmission Mode preferred for + signaling channel (O) +TSPC_AVDTP_18_2 False Streaming Mode preferred for Media Transport + channel (O) +TSPC_AVDTP_18_3 False FCS Option (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_AVDTP_18_1 is supported, otherwise Optional. +-------------------------------------------------------------------------------
diff --git a/android/pics-avrcp.txt b/android/pics-avrcp.txt new file mode 100644 index 0000000..7bd68fa --- /dev/null +++ b/android/pics-avrcp.txt
@@ -0,0 +1,644 @@ +AVRCP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory if such role selected +O - optional + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_1_1 True (*) Role: Controller (CT) (C.1) +TSPC_AVRCP_1_2 True (*) Role: Target (TG) (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Controller Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_2_1 False (*) CT: Initiating connection establishment (M) +TSPC_AVRCP_2_2 False (*) CT: Accepting connection establishment for + control initiated by TG (M) +TSPC_AVRCP_2_3 False (*) CT: Initiating connection release (M) +TSPC_AVRCP_2_4 False (*) CT: Accepting connection release for control + initiated by TG (M) +TSPC_AVRCP_2_5 False CT: Sending UNIT INFO (O) +TSPC_AVRCP_2_6 False CT: Sending SUBUNIT INFO (O) +TSPC_AVRCP_2_7 False CT: Sending PASS THROUGH command category 1 + (C.1) +TSPC_AVRCP_2_8 False CT: Sending PASS THROUGH command category 2 + (C.1) +TSPC_AVRCP_2_9 False CT: Sending PASS THROUGH command category 3 + (C.1) +TSPC_AVRCP_2_10 False CT: Sending PASS THROUGH command category 4 + (C.1) +TSPC_AVRCP_2_11 False CT: Get Capabilities (O) +TSPC_AVRCP_2_12 False CT: List Player Application Setting + Attributes (C.9) +TSPC_AVRCP_2_13 False CT: List Player Application Setting Values (O) +TSPC_AVRCP_2_14 False CT: Get Current Player Application Setting + (C.10) +TSPC_AVRCP_2_15 False CT: Set Player Application Setting Value (C.10) +TSPC_AVRCP_2_16 False CT: Get Player Application Setting + Attribute Text (O) +TSPC_AVRCP_2_17 False CT: Get Player Application Setting Value Text + (O) +TSPC_AVRCP_2_18 False CT: Inform Displayable Character Set (O) +TSPC_AVRCP_2_19 False CT: Inform Battery Status of CT (O) +TSPC_AVRCP_2_20 False CT: Get Element Attributes (O) +TSPC_AVRCP_2_21 False CT: Get Play Status (O) +TSPC_AVRCP_2_22 False CT: Register Notification (C.11) +TSPC_AVRCP_2_23 False CT: Request Continuing Response (C.2) +TSPC_AVRCP_2_24 False CT: Abort Continuing Response (C.2) +TSPC_AVRCP_2_25 False CT: Next Group (C.12) +TSPC_AVRCP_2_26 False CT: Previous Group (C.12) +TSPC_AVRCP_2_27 False CT: Media Player Selection (O) +TSPC_AVRCP_2_28 False CT: SetAddressedPlayer (O) +TSPC_AVRCP_2_29 False CT: GetFolderItems(MediaPlayerList) (C.5) +TSPC_AVRCP_2_29b False CT: GetTotalNumberOfItems(MediaPlayerList) (C.5) +TSPC_AVRCP_2_30 False CT: EVENT_AVAILABLE_PLAYERS_CHANGED (O) +TSPC_AVRCP_2_31 False CT: EVENT_ADDRESSED_PLAYER_CHANGED (O) +TSPC_AVRCP_2_32 False CT: Browsing (O) +TSPC_AVRCP_2_33 False CT: SetBrowsedPlayer (C.4) +TSPC_AVRCP_2_34 False CT: ChangePath (C.4) +TSPC_AVRCP_2_35 False CT: GetFolderItems(Filesystem) (C.4) +TSPC_AVRCP_2_35b False CT: GetTotalNumberOfItems(Filesystem) (C.4) +TSPC_AVRCP_2_36 False CT: GetItemAttributes (O) +TSPC_AVRCP_2_37 False CT: PlayItem(Filesystem) (C.4) +TSPC_AVRCP_2_38 False CT: EVENT_UIDS_CHANGED (O) +TSPC_AVRCP_2_39 False CT: Searching (O) +TSPC_AVRCP_2_40 False CT: Search (C.7) +TSPC_AVRCP_2_41 False CT: GetFolderItems(Search Results) (C.7) +TSPC_AVRCP_2_41b False CT: GetTotalNumberOfItems(Search Results) (C.7) +TSPC_AVRCP_2_42 False CT: PlayItem(SearchResultList) (C.7) +TSPC_AVRCP_2_43 False CT: NowPlaying (C.8) +TSPC_AVRCP_2_44 False CT: GetFolderItems(NowPlayingList) (C.8) +TSPC_AVRCP_2_44b False CT: GetTotalNumberOfItems(NowPlayingList) (C.8) +TSPC_AVRCP_2_45 False CT: PlayItem(NowPlayingList) (C.8) +TSPC_AVRCP_2_46 False CT: AddToNowPlaying (O) +TSPC_AVRCP_2_47 False CT: EVENT_NOW_PLAYING_CONTENT_CHANGED (O) +TSPC_AVRCP_2_48 False CT: Playable Folders (O) +TSPC_AVRCP_2_49 True (*) CT: Absolute Volume (C.3) +TSPC_AVRCP_2_50 True (*) CT: SetAbsoluteVolume (C.3) +TSPC_AVRCP_2_51 True (*) CT: NotifyVolumeChange (C.3) +TSPC_AVRCP_2_52 False (*) CT: Discoverable Mode (M) +TSPC_AVRCP_2_53 False CT: PASSTHROUGH operation supporting press + and hold (O) +TSPC_AVRCP_2_54 False CT: Cover Art (O) +TSPC_AVRCP_2_55 False CT: GetImageProperties (C.14) +TSPC_AVRCP_2_56 False CT: GetImage (C.13) +TSPC_AVRCP_2_57 False CT: GetLinkedThumbnail (C.13) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined categories + (TSPC_AVRCP_2_7 through TSPC_AVRCP_2_10). +C.2: Mandatory to support at least one of TSPC_AVRCP_2_23 or TSPC_AVRC_2_24 + if TSPC_AVRCP_2_20 is supported, otherwise Optional. +C.3: Mandatory if TSPC_AVRCP_2_8 is supported, otherwise Excluded. +C.4: Mandatory if TSPC_AVRCP_2_32 is supported, otherwise Excluded. +C.5: Mandatory if TSPC_AVRCP_2_27 is supported, otherwise Excluded. +C.7: Mandatory if item TSPC_AVRCP_2_39 is supported, Excluded otherwise. +C.8: Mandatory if TSPC_AVRCP_2_32 is supported, otherwise Excluded. +C.9: Mandatory to support if Player Application Settings feature is supported. + If any item TSPC_AVRCP_2_13 through TSPC_AVRCP_2_15 is supported it is + required to claim support for this feature in accordance with Player + Application Settings support requirements, otherwise Optional. +C.10: Mandatory to support either Get or Set Player Application Settings + (TSPC_AVRCP_2_14 or TSPC_AVRCP_2_15) if List Player Application Setting + Attributes (TSPC_AVRCP_2_12) is supported. Either TSPC_AVRCP_2_14 + or TSPC_AVRCP_2_15 must be supported if Player Application Settings + feature is supported, in accordance with Player Application Settings + support requirements. +C.11: Mandatory if TSPC_AVRCP_2_20 or TSPC_AVRCP_2_49 is supported, otherwise + Optional. +C.12: Mandatory if Basic Group Navigation Feature supported. If any item + TSPC_AVRCP_2_25 or TSPC_AVRCP_2_26 is supported it is mandatory to + support both, in accordance with Basic Group Navigation support + requirements, otherwise Excluded. +C.13: Mandatory to support at least one of the functions if TSPC_AVRCP_2_54 + (Cover Art) is support, otherwise Excluded. +C.14: Optional if TSPC_AVRCP_2_54 (Cover Art) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Controller Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_2b_1 False CT: AVRCP v1.0 (C.1) +TSPC_AVRCP_2b_2 False CT: AVRCP v1.3 (C.1) +TSPC_AVRCP_2b_3 False CT: AVRCP v1.4 (C.1) +TSPC_AVRCP_2b_4 False CT: AVRCP v1.5 (C.1) +TSPC_AVRCP_2b_5 False CT: AVRCP v1.6 (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the profile versions if + Controller role supported (SPC_AVRCP_1_1). +------------------------------------------------------------------------------- + + + Operation_id of Category 1 for CT +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_3_1 False CT: category 1 - Operation id: 0 (C.1) +TSPC_AVRCP_3_2 False CT: category 1 - Operation id: 1 (C.1) +TSPC_AVRCP_3_3 False CT: category 1 - Operation id: 2 (C.1) +TSPC_AVRCP_3_4 False CT: category 1 - Operation id: 3 (C.1) +TSPC_AVRCP_3_5 False CT: category 1 - Operation id: 4 (C.1) +TSPC_AVRCP_3_6 False CT: category 1 - Operation id: 5 (C.1) +TSPC_AVRCP_3_7 False CT: category 1 - Operation id: 6 (C.1) +TSPC_AVRCP_3_8 False CT: category 1 - Operation id: 7 (C.1) +TSPC_AVRCP_3_9 False CT: category 1 - Operation id: 8 (C.1) +TSPC_AVRCP_3_10 False CT: category 1 - Operation id: 9 (C.1) +TSPC_AVRCP_3_11 False CT: category 1 - Operation id: dot (C.1) +TSPC_AVRCP_3_12 False CT: category 1 - Operation id: enter (C.1) +TSPC_AVRCP_3_13 False CT: category 1 - Operation id: clear (C.1) +TSPC_AVRCP_3_14 False CT: category 1 - Operation id: sound_select + (C.1) +TSPC_AVRCP_3_15 False CT: category 1 - Operation id: input_select + (C.1) +TSPC_AVRCP_3_16 False CT: category 1 - Operation id: + display_information (C.1) +TSPC_AVRCP_3_17 False CT: category 1 - Operation id: help (C.1) +TSPC_AVRCP_3_18 False CT: category 1 - Operation id: power (C.1) +TSPC_AVRCP_3_19 False CT: category 1 - Operation id: play (C.1) +TSPC_AVRCP_3_20 False CT: category 1 - Operation id: stop (C.1) +TSPC_AVRCP_3_21 False CT: category 1 - Operation id: pause (C.1) +TSPC_AVRCP_3_22 False CT: category 1 - Operation id: record (C.1) +TSPC_AVRCP_3_23 False CT: category 1 - Operation id: rewind (C.1) +TSPC_AVRCP_3_24 False CT: category 1 - Operation id: fast_forward + (C.1) +TSPC_AVRCP_3_25 False CT: category 1 - Operation id: eject (C.1) +TSPC_AVRCP_3_26 False CT: category 1 - Operation id: forward (C.1) +TSPC_AVRCP_3_27 False CT: category 1 - Operation id: backward (C.1) +TSPC_AVRCP_3_28 False CT: category 1 - Operation id: angle (C.1) +TSPC_AVRCP_3_29 False CT: category 1 - Operation id: subpicture (C.1) +TSPC_AVRCP_3_30 False CT: category 1 - Operation id: F1 (C.1) +TSPC_AVRCP_3_31 False CT: category 1 - Operation id: F2 (C.1) +TSPC_AVRCP_3_32 False CT: category 1 - Operation id: F3 (C.1) +TSPC_AVRCP_3_33 False CT: category 1 - Operation id: F4 (C.1) +TSPC_AVRCP_3_34 False CT: category 1 - Operation id: vendor_unique + (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of these operation_ids if the device + supports category 1 (TSPC_AVRCP_2_7). +------------------------------------------------------------------------------- + + + Operation_id of category 2 for CT +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_4_1 False CT: category 2 - Operation id: 0 (C.1) +TSPC_AVRCP_4_2 False CT: category 2 - Operation id: 1 (C.1) +TSPC_AVRCP_4_3 False CT: category 2 - Operation id: 2 (C.1) +TSPC_AVRCP_4_4 False CT: category 2 - Operation id: 3 (C.1) +TSPC_AVRCP_4_5 False CT: category 2 - Operation id: 4 (C.1) +TSPC_AVRCP_4_6 False CT: category 2 - Operation id: 5 (C.1) +TSPC_AVRCP_4_7 False CT: category 2 - Operation id: 6 (C.1) +TSPC_AVRCP_4_8 False CT: category 2 - Operation id: 7 (C.1) +TSPC_AVRCP_4_9 False CT: category 2 - Operation id: 8 (C.1) +TSPC_AVRCP_4_10 False CT: category 2 - Operation id: 9 (C.1) +TSPC_AVRCP_4_11 False CT: category 2 - Operation id: dot (C.1) +TSPC_AVRCP_4_12 False CT: category 2 - Operation id: enter (C.1) +TSPC_AVRCP_4_13 False CT: category 2 - Operation id: clear (C.1) +TSPC_AVRCP_4_14 False CT: category 2 - Operation id: sound_select + (C.1) +TSPC_AVRCP_4_15 False CT: category 2 - Operation id: input_select + (C.1) +TSPC_AVRCP_4_16 False CT: category 2 - Operation id: + display_information (C.1) +TSPC_AVRCP_4_17 False CT: category 2 - Operation id: help (C.1) +TSPC_AVRCP_4_18 False CT: category 2 - Operation id: power (C.1) +TSPC_AVRCP_4_19 False CT: category 2 - Operation id: volume_up (C.1) +TSPC_AVRCP_4_20 False CT: category 2 - Operation id: volume_down (C.1) +TSPC_AVRCP_4_21 False CT: category 2 - Operation id: mute (C.1) +TSPC_AVRCP_4_22 False CT: category 2 - Operation id: F1 (C.1) +TSPC_AVRCP_4_23 False CT: category 2 - Operation id: F2 (C.1) +TSPC_AVRCP_4_24 False CT: category 2 - Operation id: F3 (C.1) +TSPC_AVRCP_4_25 False CT: category 2 - Operation id: F4 (C.1) +TSPC_AVRCP_4_26 False CT: category 2 - Operation id: vendor_unique + (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of these operation_ids if the device + supports category 2 (TSPC_AVRCP_2_8). +------------------------------------------------------------------------------- + + + Operation_id of category 3 for CT +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_5_1 False CT: category 3 - Operation id: 0 (C.1) +TSPC_AVRCP_5_2 False CT: category 3 - Operation id: 1 (C.1) +TSPC_AVRCP_5_3 False CT: category 3 - Operation id: 2 (C.1) +TSPC_AVRCP_5_4 False CT: category 3 - Operation id: 3 (C.1) +TSPC_AVRCP_5_5 False CT: category 3 - Operation id: 4 (C.1) +TSPC_AVRCP_5_6 False CT: category 3 - Operation id: 5 (C.1) +TSPC_AVRCP_5_7 False CT: category 3 - Operation id: 6 (C.1) +TSPC_AVRCP_5_8 False CT: category 3 - Operation id: 7 (C.1) +TSPC_AVRCP_5_9 False CT: category 3 - Operation id: 8 (C.1) +TSPC_AVRCP_5_10 False CT: category 3 - Operation id: 9 (C.1) +TSPC_AVRCP_5_11 False CT: category 3 - Operation id: dot (C.1) +TSPC_AVRCP_5_12 False CT: category 3 - Operation id: enter (C.1) +TSPC_AVRCP_5_13 False CT: category 3 - Operation id: clear (C.1) +TSPC_AVRCP_5_14 False CT: category 3 - Operation id: channel up (C.1) +TSPC_AVRCP_5_15 False CT: category 3 - Operation id: channel down + (C.1) +TSPC_AVRCP_5_16 False CT: category 3 - Operation id: previous channel + (C.1) +TSPC_AVRCP_5_17 False CT: category 3 - Operation id: sound_select + (C.1) +TSPC_AVRCP_5_18 False CT: category 3 - Operation id: input_select + (C.1) +TSPC_AVRCP_5_19 False CT: category 3 - Operation id: + display_information (C.1) +TSPC_AVRCP_5_20 False CT: category 3 - Operation id: help (C.1) +TSPC_AVRCP_5_21 False CT: category 3 - Operation id: power (C.1) +TSPC_AVRCP_5_22 False CT: category 3 - Operation id: angle (C.1) +TSPC_AVRCP_5_23 False CT: category 3 - Operation id: subpicture(C.1) +TSPC_AVRCP_5_24 False CT: category 3 - Operation id: F1 (C.1) +TSPC_AVRCP_5_25 False CT: category 3 - Operation id: F2 (C.1) +TSPC_AVRCP_5_26 False CT: category 3 - Operation id: F3 (C.1) +TSPC_AVRCP_5_27 False CT: category 3 - Operation id: F4 (C.1) +TSPC_AVRCP_5_28 False CT: category 3 - Operation id: vendor_unique + (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of these operation_ids if the device + supports category 3 (TSPC_AVRCP_2_9). +------------------------------------------------------------------------------- + + + Operation_id of category 4 for CT +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_6_1 False CT: category 4 - Operation id: select (C.1) +TSPC_AVRCP_6_2 False CT: category 4 - Operation id: up (C.1) +TSPC_AVRCP_6_3 False CT: category 4 - Operation id: down (C.1) +TSPC_AVRCP_6_4 False CT: category 4 - Operation id: left (C.1) +TSPC_AVRCP_6_5 False CT: category 4 - Operation id: right (C.1) +TSPC_AVRCP_6_6 False CT: category 4 - Operation id: right up (C.1) +TSPC_AVRCP_6_7 False CT: category 4 - Operation id: right down (C.1) +TSPC_AVRCP_6_8 False CT: category 4 - Operation id: left up (C.1) +TSPC_AVRCP_6_9 False CT: category 4 - Operation id: left down (C.1) +TSPC_AVRCP_6_10 False CT: category 4 - Operation id: root menu (C.1) +TSPC_AVRCP_6_11 False CT: category 4 - Operation id: setup menu (C.1) +TSPC_AVRCP_6_12 False CT: category 4 - Operation id: contents menu + (C.1) +TSPC_AVRCP_6_13 False CT: category 4 - Operation id: favorite menu + (C.1) +TSPC_AVRCP_6_14 False CT: category 4 - Operation id: exit (C.1) +TSPC_AVRCP_6_15 False CT: category 4 - Operation id: 0 (C.1) +TSPC_AVRCP_6_16 False CT: category 4 - Operation id: 1 (C.1) +TSPC_AVRCP_6_17 False CT: category 4 - Operation id: 2 (C.1) +TSPC_AVRCP_6_18 False CT: category 4 - Operation id: 3 (C.1) +TSPC_AVRCP_6_19 False CT: category 4 - Operation id: 4 (C.1) +TSPC_AVRCP_6_20 False CT: category 4 - Operation id: 5 (C.1) +TSPC_AVRCP_6_21 False CT: category 4 - Operation id: 6 (C.1) +TSPC_AVRCP_6_22 False CT: category 4 - Operation id: 7 (C.1) +TSPC_AVRCP_6_23 False CT: category 4 - Operation id: 8 (C.1) +TSPC_AVRCP_6_24 False CT: category 4 - Operation id: 9 (C.1) +TSPC_AVRCP_6_25 False CT: category 4 - Operation id: dot (C.1) +TSPC_AVRCP_6_26 False CT: category 4 - Operation id: enter (C.1) +TSPC_AVRCP_6_27 False CT: category 4 - Operation id: clear (C.1) +TSPC_AVRCP_6_28 False CT: category 4 - Operation id: + display_information (C.1) +TSPC_AVRCP_6_29 False CT: category 4 - Operation id: help (C.1) +TSPC_AVRCP_6_30 False CT: category 4 - Operation id: page up (C.1) +TSPC_AVRCP_6_31 False CT: category 4 - Operation id: page down (C.1) +TSPC_AVRCP_6_32 False CT: category 4 - Operation id: power (C.1) +TSPC_AVRCP_6_33 False CT: category 4 - Operation id: F1 (C.1) +TSPC_AVRCP_6_34 False CT: category 4 - Operation id: F2 (C.1) +TSPC_AVRCP_6_35 False CT: category 4 - Operation id: F3 (C.1) +TSPC_AVRCP_6_36 False CT: category 4 - Operation id: F4 (C.1) +TSPC_AVRCP_6_37 False CT: category 4 - Operation id: vendor_unique + (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of these operation_ids if the device + supports category 4 (TSPC_AVRCP_2_10). +------------------------------------------------------------------------------- + + + Target Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_7_1 True (*) TG: Initiating connection establishment for + Control (O) +TSPC_AVRCP_7_2 True TG: Accept connection establishment for Control + initiated by CT (M) +TSPC_AVRCP_7_3 True (*) TG: Initiating connection release (M) +TSPC_AVRCP_7_4 True TG: Accepting connection release (M) +TSPC_AVRCP_7_5 True TG: Receiving UNIT INFO (M) +TSPC_AVRCP_7_6 True TG: Receiving SUBUNIT INFO (M) +TSPC_AVRCP_7_7 True (*) TG: Receiving PASS THROUGH command category 1 + (C.1) +TSPC_AVRCP_7_8 True (*) TG: Receiving PASS THROUGH command category 2 + (C.1) +TSPC_AVRCP_7_9 False TG: Receiving PASS THROUGH command category 3 + (C.1) +TSPC_AVRCP_7_10 False TG: Receiving PASS THROUGH command category 4 + (C.1) +TSPC_AVRCP_7_11 True (*) TG: Get Capabilities Response (C.3) +TSPC_AVRCP_7_12 False TG: List Player Application Settings Attributes + Response (C.14) +TSPC_AVRCP_7_13 False TG: List Player Application Setting Values + Response (C.14) +TSPC_AVRCP_7_14 False TG: Get Current Player Application Settings + Value Response (C.14) +TSPC_AVRCP_7_15 False TG: Set Player Application Setting Value + Response (C.14) +TSPC_AVRCP_7_16 False TG: Get Player Application Setting Attribute + Text Response (O) +TSPC_AVRCP_7_17 False TG: Get Player Application Setting Value Text + Response (O) +TSPC_AVRCP_7_18 False TG: Inform Displayable Character Set Response + (O) +TSPC_AVRCP_7_19 False TG: Inform Battery Status Of CT Response (O) +TSPC_AVRCP_7_20 True (*) TG: Get Element Attributes Response (C.3) +TSPC_AVRCP_7_21 True (*) TG: Get Play Status Response (C.2) +TSPC_AVRCP_7_22 True (*) TG: Register Notification Response (C.12) +TSPC_AVRCP_7_23 True (*) TG: Notify Event Response: + PLAYBACK_STATUS_CHANGED (C.4) +TSPC_AVRCP_7_24 True (*) TG: Notify Event Response: TRACK_CHANGED (C.4) +TSPC_AVRCP_7_25 False TG: Notify Event Response: TRACK_REACHED_END (O) +TSPC_AVRCP_7_26 False TG: Notify Event Response: TRACK_REACHED_START + (O) +TSPC_AVRCP_7_27 False TG: Notify Event Response: PLAYBACK_POS_CHANGED + (O) +TSPC_AVRCP_7_28 False TG: Notify Event Response: BATT_STATUS_CHANGED + (O) +TSPC_AVRCP_7_29 False TG: Notify Event Response: SYSTEM_STATUS_CHANGED + (O) +TSPC_AVRCP_7_30 False TG: Notify Event Response: + PLAYER_APPLICATION_SETTING_CHANGED (O) +TSPC_AVRCP_7_31 True (*) TG: Request Continuing Response (C.2) +TSPC_AVRCP_7_32 True (*) TG: Abort ContinuingResponse Response (C.2) +TSPC_AVRCP_7_34 False TG: Next Group (C.15) +TSPC_AVRCP_7_35 False TG: Previous Group (C.15) +TSPC_AVRCP_7_36 False TG: Media Player Selection (C.8) +TSPC_AVRCP_7_37 False TG: SetAddressedPlayer (C.8) +TSPC_AVRCP_7_38 False TG: GetFolderItems(MediaPlayerList) (C.8) +TSPC_AVRCP_7_38b False TG: GetTotalNumberOfItems(MediaPlayerList) (C.8) +TSPC_AVRCP_7_39 False TG: EVENT_AVAILABLE_PLAYERS_CHANGED (C.8) +TSPC_AVRCP_7_40 False TG: EVENT_ADDRESSED_PLAYER_CHANGED (C.8) +TSPC_AVRCP_7_41 False TG: Supports Multiple Players (O) +TSPC_AVRCP_7_42 False TG: Browsing (O) +TSPC_AVRCP_7_42a False TG: Initiating connection establishment for + browsing channel (O) +TSPC_AVRCP_7_43 False TG: SetBrowsedPlayer (C.6) +TSPC_AVRCP_7_44 False TG: ChangePath (C.6) +TSPC_AVRCP_7_45 False TG: GetFolderItems(Filesystem) (C.6) +TSPC_AVRCP_7_45b False TG: GetTotalNumberOfItems(Filesystem) (C.6) +TSPC_AVRCP_7_46 False TG: GetItemAttributes (C.6) +TSPC_AVRCP_7_47 False TG: PlayItem(Filesystem) (C.6) +TSPC_AVRCP_7_48 False TG: EVENT_UIDS_CHANGED (C.9) +TSPC_AVRCP_7_49 False TG: Database Aware Players (O) +TSPC_AVRCP_7_50 False TG: Searching (O) +TSPC_AVRCP_7_51 False TG: Search (C.10) +TSPC_AVRCP_7_52 False TG: GetFolderItems(Search Results) (C.10) +TSPC_AVRCP_7_52b False TG: GetTotalNumberOfItems(Search Results) (C.10) +TSPC_AVRCP_7_53 False TG: PlayItem(SearchResultList) (C.10) +TSPC_AVRCP_7_54 False TG: NowPlaying (C.11) +TSPC_AVRCP_7_55 False TG: GetFolderItems(NowPlayingList) (C.11) +TSPC_AVRCP_7_55b False TG: GetTotalNumberOfItems(NowPlayingList) (C.11) +TSPC_AVRCP_7_56 False TG: PlayItem(NowPlayingList) (C.11) +TSPC_AVRCP_7_57 False TG: AddToNowPlaying (O) +TSPC_AVRCP_7_58 False TG: EVENT_NOW_PLAYING_CONTENT_CHANGED (C.11) +TSPC_AVRCP_7_59 False TG: Playable Folders (O) +TSPC_AVRCP_7_60 False TG: Absolute Volume (C.5) +TSPC_AVRCP_7_61 False TG: SetAbsoluteVolume (C.5) +TSPC_AVRCP_7_62 False TG: NotifyVolumeChange (C.5) +TSPC_AVRCP_7_63 False TG: Error Response (O) +TSPC_AVRCP_7_64 False TG: General Reject (C.13) +TSPC_AVRCP_7_65 True TG: Discoverable Mode (M) +TSPC_AVRCP_7_66 False TG: PASSTHROUGH operation supporting press + and hold (O) +TSPC_AVRCP_7_67 False TG: Cover Art (O) +TSPC_AVRCP_7_68 False TG: GetImageProperties (C.16) +TSPC_AVRCP_7_69 False TG: GetImage (C.16) +TSPC_AVRCP_7_70 False TG: GetLinkedThumbnail (C.16) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the categories. Supported + operation_id's are shown in Table 8 to Table 11. +C.2: Mandatory if 7/20 is supported, otherwise Optional. +C.3: Mandatory if 7/7 is supported, otherwise Optional. +C.4: Mandatory if 7/22 and 7/20 is supported, otherwise Optional. +C.5: Mandatory if 7/8 is supported, otherwise Excluded. +C.6: Mandatory if 7/42 is supported, otherwise Excluded. +C.7: Mandatory if 7/36 is supported, otherwise Excluded. +C.8: Mandatory if (7/7 or 7/9) is supported, otherwise Excluded. +C.9: Mandatory if 7/49 is supported, otherwise Optional. +C.10: Mandatory if 7/50 is supported, otherwise Excluded. +C.11: Mandatory if 7/42 is supported, otherwise Optional. +C.12: Mandatory if 7/7 or (7/8 AND 7/60) or 7/9 is supported, otherwise Optional +C.13: Mandatory if 7/7 or 7/9 or 7/42 is supported, otherwise Optional. +C.14: Mandatory if Player Application Settings Feature supported. If any item + 7/12 – 7/15 is supported, all items 7/12 – 7/15 shall be supported, + in accordance with Player Application Settings Feature support + requirements, otherwise Excluded. +C.15: Mandatory if Basic Group Navigation Feature supported. If any item + 7/34 or 7/35 is supported it is mandatory to support both, + in accordance with Basic Group Navigation support requirements, + otherwise Excluded. +C.16: Mandatory if 7/67 (Cover Art) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + Target Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_7b_1 False TG: AVRCP v1.0 (C.1) +TSPC_AVRCP_7b_2 False TG: AVRCP v1.3 (C.1) +TSPC_AVRCP_7b_3 False TG: AVRCP v1.4 (C.1) +TSPC_AVRCP_7b_4 True (*) TG: AVRCP v1.5 (C.1) +TSPC_AVRCP_7b_5 False TG: AVRCP v1.6 (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the profile versions. +------------------------------------------------------------------------------- + + + operation_id of category 1 for TG +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_8_1 False TG: category 1 - Operation id: 0 (O) +TSPC_AVRCP_8_2 False TG: category 1 - Operation id: 1 (O) +TSPC_AVRCP_8_3 False TG: category 1 - Operation id: 2 (O) +TSPC_AVRCP_8_4 False TG: category 1 - Operation id: 3 (O) +TSPC_AVRCP_8_5 False TG: category 1 - Operation id: 4 (O) +TSPC_AVRCP_8_6 False TG: category 1 - Operation id: 5 (O) +TSPC_AVRCP_8_7 False TG: category 1 - Operation id: 6 (O) +TSPC_AVRCP_8_8 False TG: category 1 - Operation id: 7 (O) +TSPC_AVRCP_8_9 False TG: category 1 - Operation id: 8 (O) +TSPC_AVRCP_8_10 False TG: category 1 - Operation id: 9 (O) +TSPC_AVRCP_8_11 False TG: category 1 - Operation id: dot (O) +TSPC_AVRCP_8_12 False TG: category 1 - Operation id: enter (O) +TSPC_AVRCP_8_13 False TG: category 1 - Operation id: clear (O) +TSPC_AVRCP_8_14 False TG: category 1 - Operation id: sound select (O) +TSPC_AVRCP_8_15 False TG: category 1 - Operation id: input select (O) +TSPC_AVRCP_8_16 False TG: category 1 - Operation id: display + information (O) +TSPC_AVRCP_8_17 False TG: category 1 - Operation id: help (O) +TSPC_AVRCP_8_18 False TG: category 1 - Operation id: power (O) +TSPC_AVRCP_8_19 True TG: category 1 - Operation id: play (M) +TSPC_AVRCP_8_20 True TG: category 1 - Operation id: stop (M) +TSPC_AVRCP_8_21 True (*) TG: category 1 - Operation id: pause (O) +TSPC_AVRCP_8_22 False TG: category 1 - Operation id: record (O) +TSPC_AVRCP_8_23 True (*) TG: category 1 - Operation id: rewind (O) +TSPC_AVRCP_8_24 True (*) TG: category 1 - Operation id: fast forward (O) +TSPC_AVRCP_8_25 False TG: category 1 - Operation id: eject (O) +TSPC_AVRCP_8_26 True (*) TG: category 1 - Operation id: forward (O) +TSPC_AVRCP_8_27 True (*) TG: category 1 - Operation id: backward (O) +TSPC_AVRCP_8_28 False TG: category 1 - Operation id: angle (O) +TSPC_AVRCP_8_29 False TG: category 1 - Operation id: subpicture (O) +TSPC_AVRCP_8_30 False TG: category 1 - Operation id: F1 (O) +TSPC_AVRCP_8_31 False TG: category 1 - Operation id: F2 (O) +TSPC_AVRCP_8_32 False TG: category 1 - Operation id: F3 (O) +TSPC_AVRCP_8_33 False TG: category 1 - Operation id: F4 (O) +TSPC_AVRCP_8_33a False TG: category 1 - Operation id: F5 (O) +TSPC_AVRCP_8_34 False TG: category 1 - Operation id: vendor unique (O) +------------------------------------------------------------------------------- + + + operation_id of category 2 for TG +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_9_1 False TG: category 2 - Operation id: 0 (O) +TSPC_AVRCP_9_2 False TG: category 2 - Operation id: 1 (O) +TSPC_AVRCP_9_3 False TG: category 2 - Operation id: 2 (O) +TSPC_AVRCP_9_4 False TG: category 2 - Operation id: 3 (O) +TSPC_AVRCP_9_5 False TG: category 2 - Operation id: 4 (O) +TSPC_AVRCP_9_6 False TG: category 2 - Operation id: 5 (O) +TSPC_AVRCP_9_7 False TG: category 2 - Operation id: 6 (O) +TSPC_AVRCP_9_8 False TG: category 2 - Operation id: 7 (O) +TSPC_AVRCP_9_9 False TG: category 2 - Operation id: 8 (O) +TSPC_AVRCP_9_10 False TG: category 2 - Operation id: 9 (O) +TSPC_AVRCP_9_11 False TG: category 2 - Operation id: dot (O) +TSPC_AVRCP_9_12 False TG: category 2 - Operation id: enter (O) +TSPC_AVRCP_9_13 False TG: category 2 - Operation id: clear (O) +TSPC_AVRCP_9_14 False TG: category 2 - Operation id: sound select (O) +TSPC_AVRCP_9_15 False TG: category 2 - Operation id: input select (O) +TSPC_AVRCP_9_16 False TG: category 2 - Operation id: display + information (O) +TSPC_AVRCP_9_17 False TG: category 2 - Operation id: help (O) +TSPC_AVRCP_9_18 False TG: category 2 - Operation id: power (O) +TSPC_AVRCP_9_19 True TG: category 2 - Operation id: volume up (C.2) +TSPC_AVRCP_9_20 True TG: category 2 - Operation id: volume down (C.2) +TSPC_AVRCP_9_21 False TG: category 2 - Operation id: mute (O) +TSPC_AVRCP_9_24 False TG: category 2 - Operation id: F1 (O) +TSPC_AVRCP_9_25 False TG: category 2 - Operation id: F2 (O) +TSPC_AVRCP_9_26 False TG: category 2 - Operation id: F3 (O) +TSPC_AVRCP_9_27 False TG: category 2 - Operation id: F4 (O) +TSPC_AVRCP_9_27a False TG: category 2 - Operation id: F5 (O) +TSPC_AVRCP_9_28 False TG: category 2 - Operation id: vendor unique (O) +------------------------------------------------------------------------------- +C.2: Mandatory to support if the device supports category 2 (TSPC_AVRCP_7_8). +------------------------------------------------------------------------------- + + + operation_id of category 3 for TG +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_10_1 False TG: category 3 - Operation id: 0 (O) +TSPC_AVRCP_10_2 False TG: category 3 - Operation id: 1 (O) +TSPC_AVRCP_10_3 False TG: category 3 - Operation id: 2 (O) +TSPC_AVRCP_10_4 False TG: category 3 - Operation id: 3 (O) +TSPC_AVRCP_10_5 False TG: category 3 - Operation id: 4 (O) +TSPC_AVRCP_10_6 False TG: category 3 - Operation id: 5 (O) +TSPC_AVRCP_10_7 False TG: category 3 - Operation id: 6 (O) +TSPC_AVRCP_10_8 False TG: category 3 - Operation id: 7 (O) +TSPC_AVRCP_10_9 False TG: category 3 - Operation id: 8 (O) +TSPC_AVRCP_10_10 False TG: category 3 - Operation id: 9 (O) +TSPC_AVRCP_10_11 False TG: category 3 - Operation id: dot (O) +TSPC_AVRCP_10_12 False TG: category 3 - Operation id: enter (O) +TSPC_AVRCP_10_13 False TG: category 3 - Operation id: clear (O) +TSPC_AVRCP_10_14 False (*) TG: category 3 - Operation id: channel up (C.3) +TSPC_AVRCP_10_15 False (*) TG: category 3 - Operation id: channel down + (C.3) +TSPC_AVRCP_10_16 False TG: category 3 - Operation id: previous channel + (O) +TSPC_AVRCP_10_17 False TG: category 3 - Operation id: sound select (O) +TSPC_AVRCP_10_18 False TG: category 3 - Operation id: input select (O) +TSPC_AVRCP_10_19 False TG: category 3 - Operation id: display + information (O) +TSPC_AVRCP_10_20 False TG: category 3 - Operation id: help (O) +TSPC_AVRCP_10_21 False TG: category 3 - Operation id: power (O) +TSPC_AVRCP_10_21a False TG: category 3 - Operation id: angle (O) +TSPC_AVRCP_10_21b False TG: category 3 - Operation id: subpicture (O) +TSPC_AVRCP_10_22 False TG: category 3 - Operation id: F1 (O) +TSPC_AVRCP_10_23 False TG: category 3 - Operation id: F2 (O) +TSPC_AVRCP_10_24 False TG: category 3 - Operation id: F3 (O) +TSPC_AVRCP_10_25 False TG: category 3 - Operation id: F4 (O) +TSPC_AVRCP_10_25a False TG: category 3 - Operation id: F5 (O) +TSPC_AVRCP_10_26 False TG: category 3 - Operation id: vendor unique (O) +------------------------------------------------------------------------------- +C.3: Mandatory to support if the device supports category 3 (TSPC_AVRCP_7_9). +------------------------------------------------------------------------------- + + + operation_id of category 4 for TG +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_AVRCP_11_1 False (*) TG: category 4 - Operation id: select (C.4) +TSPC_AVRCP_11_2 False (*) TG: category 4 - Operation id: up (C.4) +TSPC_AVRCP_11_3 False (*) TG: category 4 - Operation id: down (C.4) +TSPC_AVRCP_11_4 False (*) TG: category 4 - Operation id: left (C.4) +TSPC_AVRCP_11_5 False (*) TG: category 4 - Operation id: right (C.4) +TSPC_AVRCP_11_6 False TG: category 4 - Operation id: right up (O) +TSPC_AVRCP_11_7 False TG: category 4 - Operation id: right down (O) +TSPC_AVRCP_11_8 False TG: category 4 - Operation id: left up (O) +TSPC_AVRCP_11_9 False TG: category 4 - Operation id: left down (O) +TSPC_AVRCP_11_10 False (*) TG: category 4 - Operation id: root menu (C.4) +TSPC_AVRCP_11_11 False TG: category 4 - Operation id: setup menu (O) +TSPC_AVRCP_11_12 False TG: category 4 - Operation id: contents menu (O) +TSPC_AVRCP_11_13 False TG: category 4 - Operation id: favorite menu (O) +TSPC_AVRCP_11_14 False TG: category 4 - Operation id: exit (O) +TSPC_AVRCP_11_15 False TG: category 4 - Operation id: 0 (O) +TSPC_AVRCP_11_16 False TG: category 4 - Operation id: 1 (O) +TSPC_AVRCP_11_17 False TG: category 4 - Operation id: 2 (O) +TSPC_AVRCP_11_18 False TG: category 4 - Operation id: 3 (O) +TSPC_AVRCP_11_19 False TG: category 4 - Operation id: 4 (O) +TSPC_AVRCP_11_20 False TG: category 4 - Operation id: 5 (O) +TSPC_AVRCP_11_21 False TG: category 4 - Operation id: 6 (O) +TSPC_AVRCP_11_22 False TG: category 4 - Operation id: 7 (O) +TSPC_AVRCP_11_23 False TG: category 4 - Operation id: 8 (O) +TSPC_AVRCP_11_24 False TG: category 4 - Operation id: 9 (O) +TSPC_AVRCP_11_25 False TG: category 4 - Operation id: dot (O) +TSPC_AVRCP_11_26 False TG: category 4 - Operation id: enter (O) +TSPC_AVRCP_11_27 False TG: category 4 - Operation id: clear (O) +TSPC_AVRCP_11_28 False TG: category 4 - Operation id: disply (O) +TSPC_AVRCP_11_29 False TG: category 4 - Operation id: help (O) +TSPC_AVRCP_11_30 False TG: category 4 - Operation id: page up (O) +TSPC_AVRCP_11_31 False TG: category 4 - Operation id: page down (O) +TSPC_AVRCP_11_32 False TG: category 4 - Operation id: power (O) +TSPC_AVRCP_11_33 False TG: category 4 - Operation id: F1 (O) +TSPC_AVRCP_11_34 False TG: category 4 - Operation id: F2 (O) +TSPC_AVRCP_11_35 False TG: category 4 - Operation id: F3 (O) +TSPC_AVRCP_11_36 False TG: category 4 - Operation id: F4 (O) +TSPC_AVRCP_11_36a False TG: category 4 - Operation id: F5 (O) +TSPC_AVRCP_11_37 False TG: category 4 - Operation id: vendor unique (O) + +TSPC_AVRCP_12_1 True General discoverable mode (M) +TSPC_AVRCP_13_1 True General discoverable mode (M) +TSPC_AVRCP_14_1 False OBEX Connect operation (C.1) +TSPC_AVRCP_14_2 False OBEX Get operation (C.1) +TSPC_AVRCP_14_3 False OBEX Disconnect operation (C.1) +TSPC_AVRCP_15_1 False OBEX Connect operation (C.1) +TSPC_AVRCP_15_2 False OBEX Get operation (C.1) +TSPC_AVRCP_15_3 False OBEX Disconnect operation (C.1) + +TSPC_ALL False Enables all test cases when set to TRUE. +------------------------------------------------------------------------------- +C.4: Mandatory to support if the device supports category 4 (TSPC_AVRCP_7_10). +-------------------------------------------------------------------------------
diff --git a/android/pics-bnep.txt b/android/pics-bnep.txt new file mode 100644 index 0000000..289e470 --- /dev/null +++ b/android/pics-bnep.txt
@@ -0,0 +1,26 @@ +BNEP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory if such role selected +O - optional + + Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_BNEP_1_1 True BNEP Connection Setup (M) +TSPC_BNEP_1_2 True (*) BNEP Data Packet Reception (M) +TSPC_BNEP_1_3 True (*) BNEP Data Packet Transmission (M) +TSPC_BNEP_1_3a True (*) BNEP Compressed Packet Transmission (O) +TSPC_BNEP_1_3b True (*) BNEP Compressed Packet Transmission Source Only + (O) +TSPC_BNEP_1_4 True BNEP Control Message Processing (M) +TSPC_BNEP_1_5 True BNEP Extension Header Processing (M) +TSPC_BNEP_1_6 True Network Protocol Filter Message Transmission (O) +TSPC_BNEP_1_7 True Multicast Address Filter Message Transmission + (O) +-------------------------------------------------------------------------------
diff --git a/android/pics-did.txt b/android/pics-did.txt new file mode 100644 index 0000000..9ecb86d --- /dev/null +++ b/android/pics-did.txt
@@ -0,0 +1,23 @@ +DID PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + + SDP Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_DID_1_1 True Specification ID (M) +TSPC_DID_1_2 True Vendor ID (M) +TSPC_DID_1_3 True Product ID (M) +TSPC_DID_1_4 True Version (M) +TSPC_DID_1_5 True Primary Record (M) +TSPC_DID_1_6 True Vendor ID Source (M) +TSPC_ALL False Turns on all the test cases +-------------------------------------------------------------------------------
diff --git a/android/pics-dis.txt b/android/pics-dis.txt new file mode 100644 index 0000000..83f25ce --- /dev/null +++ b/android/pics-dis.txt
@@ -0,0 +1,59 @@ +DIS PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults + +M - mandatory +O - optional + + Transport Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_DIS_1_1 True Service supported over BR/EDR (C.1) +TSPC_DIS_1_2 True Service supported over LE (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of TSPC_DIS_1_1 or TSPC_DIS_1_2. +------------------------------------------------------------------------------- + + + Service Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_DIS_2_1 True Device Information Service (M) +TSPC_DIS_2_2 True Manufacturer Name String Characteristic (O) +TSPC_DIS_2_3 True Model Number String Characteristic (O) +TSPC_DIS_2_4 True Serial Number String Characteristic (O) +TSPC_DIS_2_5 True Hardware Revision String Characteristic (O) +TSPC_DIS_2_6 True Firmware Revision String Characteristic (O) +TSPC_DIS_2_7 True Software Revision String Characteristic (O) +TSPC_DIS_2_8 True System ID Characteristic (O) +TSPC_DIS_2_9 False (*) IEEE 11073-20601 Regulatory Certification + Data List Characteristic (O) +TSPC_DIS_2_10 True SDP Interoperability (C.1) +TSPC_DIS_2_11 True PnP ID (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_DIS_1_1 (BR/EDR) is supported, otherwise excluded. +------------------------------------------------------------------------------- + + + GATT Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_DIS_3_1 True Generic Attribute Profile Server (M) +------------------------------------------------------------------------------- + + + SDP Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_DIS_4_1 True Support for server role (M) +TSPC_DIS_4_2 True ProtocolDescriptorList (M) +TSPC_DIS_4_3 True BrowseGroupList (M) +------------------------------------------------------------------------------- +Note: Marked as False as TSPC_DIS_1_1 is set to False +-------------------------------------------------------------------------------
diff --git a/android/pics-gap.txt b/android/pics-gap.txt new file mode 100644 index 0000000..3775995 --- /dev/null +++ b/android/pics-gap.txt
@@ -0,0 +1,788 @@ +GAP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults + +M - mandatory +O - optional + + Device Configuration +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_0_1 False (*) BR/EDR (C.1) +TSPC_GAP_0_2 False (*) LE (C.2) +TSPC_GAP_0_3 True BR/EDR/LE (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory if ('End Product' or 'Host Subsystem') and ('BR Host' or + 'BR/HS Host') are Supported ('End Product' or 'Host Subsystem' with 'BR' + or 'BR/HS Host' CC), otherwise excluded. Optional for + 'Component (Tested)' or 'Component (Non-Tested)'. +C.2: Mandatory if ('End Product' or 'Host Subsystem') and ('LE Host') are + Supported (End Product or Host Subsystem with LE Host CC), + otherwise excluded. Optional for 'Component (Tested)' or + 'Component (Non-Tested)'. +C.3: Mandatory if ('End Product' or 'Host Subsystem') and ('BR/LE Host' or + 'BR/HS/LE Host') are Supported (End Product or Host Subsystem with + BR/LE or BR/HS/LE Host CC), otherwise excluded. + Optional for 'Component (Tested)' or 'Component (Non-tested)'. +Note - Only one transport shall be supported. +------------------------------------------------------------------------------- + + + Version Configuration +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_0A_1 True Core Specification Addendum 3 (CSA3), GAP + Connection Parameters Changes, + Authentication and Lost Bond Changes, + Private Addressing Changes, Dual Mode + Addressing Changes, + Adopted 24 July 2012 (C.1) +TSPC_GAP_0A_2 True Core Specification Addendum 4 (CSA4) +TSPC_GAP_0A_3 True Core Spec version 4.1 (Core v4.1) GAP Connection + Parameters Changes, Authentication and + Lost Bond Changes, Private Addressing + Changes, Dual Mode Addressing Changes, + Adopted 03 December 2013 +------------------------------------------------------------------------------- +C.1: Mandatory if 'CSA3 Adopted 24 July 2012' is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Modes +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_1_1 True Non-discoverable mode (C.1) +TSPC_GAP_1_2 True Limited-discoverable Mode (O) +TSPC_GAP_1_3 True General-discoverable mode (O) +TSPC_GAP_1_4 True Non-connectable mode (O) +TSPC_GAP_1_5 True Connectable mode (M) +TSPC_GAP_1_6 True Non-bondable mode (O) +TSPC_GAP_1_7 True Bondable mode (C.2) +TSPC_GAP_1_8 False (*) Non-Synchronizable Mode (C.3) +TSPC_GAP_1_9 False (*) Synchronizable Mode (C.4) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_0_2 is supported, otherwise Optional. +C.2: Mandatory if TSPC_GAP_3_5 is supported, otherwise Optional. +C.3: Mandatory if TSPC_GAP_0A_2 or TSPC_GAP_0A_3 and is supported, otherwise + Excluded. +C.4: Optional if TSPC_GAP_0A_2 or later is supported; Mandatory if TSPC_GAP_0A_2 + or later and BB 3a/1 (Connectionless Slave Broadcast Transmitter) are + supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Security Aspects +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_2_1 True Authentication procedure (C.1) +TSPC_GAP_2_2 True Support of LMP-Authentication (M) +TSPC_GAP_2_3 True Initiate LMP-Authentication (C.5) +TSPC_GAP_2_4 False (*) Security mode 1 (C.2) +TSPC_GAP_2_5 True Security mode 2 (O) +TSPC_GAP_2_6 False (*) Security mode 3 (C.7) +TSPC_GAP_2_7 True Security mode 4 (C.4) +TSPC_GAP_2_8 True Support of Authenticated link key (C.6) +TSPC_GAP_2_9 True Support of Unauthenticated link key (C.6) +TSPC_GAP_2_10 True No security (C.6) +TSPC_GAP_2_11 False (*) Secure Connections Only Mode (O) +------------------------------------------------------------------------------- +C.1: Mandatory If (TSPC_GAP_2_5 or TSPC_GAP_2_6) is supported, otherwise + Optional. +Note 1: The Authentication Procedure in item GAP, TSPC_GAP_2_1 is the one + described in Fig. 5.1 on page 198 in the GAP Profile Specification and + not the LMP-Authenticaion. +C.2: Excluded if TSPC_GAP_2_7 is supported, otherwise Optional. +C.5: Mandatory If (TSPC_GAP_2_5 or TSPC_GAP_2_6 or TSPC_GAP_2_7) is supported, + otherwise Optional. +C.4: Mandatory if (Core Spec 2.1 or later) is supported, otherwise Excluded. +Note 2. If a Core 2.0 and earlier design claims to support secure communcation + it should support either Security mode 2 or 3. +Note 3. A Core 2.1 or later device shall always support secure communication + in Security Mode 4, and shall use that mode to connect with another + Core 2.1 or later device. It shall use Security Mode 2 only for + backward compatibility purposes with Core 2.0 and earlier devices. + Security Mode 1 is excluded for Core 2.1 or later devices based on + condition C.2. +C.6: If TSPC_GAP_2_7 is supported then at least one of (TSPC_GAP_2_8 or + TSPC_GAP_2_9 or TSPC_GAP_2_10) is Mandatory, otherwise Excluded. +C.7: Excluded if TSPC_GAP_2_7 is supported, otherwise Optional. +------------------------------------------------------------------------------- + + + Idle Mode Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_3_1 True Initiation of general inquiry (C.1) +TSPC_GAP_3_2 True Initiation of limited inquiry (C.1) +TSPC_GAP_3_3 True Initiation of name discover (O) +TSPC_GAP_3_4 True Initiation of device discovery (O) +TSPC_GAP_3_5 True Initiation of general bonding (O) +TSPC_GAP_3_6 True Initiation of dedicated bonding (O) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of TSPC_GAP_3_1 or TSPC_GAP_3_2 if + TSPC_GAP_3_5 is supported, otherwise Optional. +------------------------------------------------------------------------------- + + + Establishment Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_4_1 True Support link establishment as initiator (M) +TSPC_GAP_4_2 True Support link establishment as acceptor (M) +TSPC_GAP_4_3 True Support channel establishment as initiator (O) +TSPC_GAP_4_4 True Support channel establishment as acceptor (M) +TSPC_GAP_4_5 True Support connection establishment as initiator + (O) +TSPC_GAP_4_6 True Support connection establishment as acceptor + (O) +TSPC_GAP_4_7 True Support synchronization establishment + as receiver (O) +------------------------------------------------------------------------------- + + + LE Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_5_1 False (*) Broadcaster (C.1) +TSPC_GAP_5_2 False (*) Observer (C.1) +TSPC_GAP_5_3 True Peripheral (C.1) +TSPC_GAP_5_4 True Central (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined roles. +Note: 'LE Roles' is applicable for LE-only configurations, but it appears that + PTS is checking this precondition also in some BR/EDR/LE tests. +------------------------------------------------------------------------------- + + + Broadcaster Physical Layer +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_6_1 False (*) Broadcaster: Transmitter (M) +TSPC_GAP_6_2 False (*) Broadcaster: Receiver (O) +------------------------------------------------------------------------------- + + + Broadcaster Link Layer States +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_7_1 False (*) Broadcaster: Standby (M) +TSPC_GAP_7_2 False (*) Broadcaster: Advertising (M) +------------------------------------------------------------------------------- + + + Broadcaster Link Layer Advertising Event Types +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_8_1 False (*) Broadcaster: Non-Connectable Undirected Event + (M) +TSPC_GAP_8_2 False (*) Broadcaster: Scannable Undirected Event (O) +------------------------------------------------------------------------------- + + + Broadcaster Link Layer Advertising Data Types +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_8A_1 False (*) AD Type-Service UUID (O) +TSPC_GAP_8A_2 False (*) AD Type-Local Name (O) +TSPC_GAP_8A_3 False (*) AD Type-Flags (O) +TSPC_GAP_8A_4 False (*) AD Type-Manufacturer Specific Data (O) +TSPC_GAP_8A_5 False (*) AD Type-TX Power Level (O) +TSPC_GAP_8A_6 False (*) AD Type-Security Manager Out of Band (OOB) (C.1) +TSPC_GAP_8A_7 False (*) AD Type-Security manager TK Value (O) +TSPC_GAP_8A_8 False (*) AD Type-Slave Connection Interval Range (O) +TSPC_GAP_8A_9 False (*) AD Type-Service Solicitation (O) +TSPC_GAP_8A_10 False (*) AD Type-Service Data (O) +TSPC_GAP_8A_11 False (*) AD Type-Appearance (O) +TSPC_GAP_8A_12 False (*) AD Type-Public Target Address (O) +TSPC_GAP_8A_13 False (*) AD Type-Random Target Address (O) +TSPC_GAP_8A_14 False (*) AD Type-Advertising Interval (O) +TSPC_GAP_8A_15 False (*) AD Type-LE Bluetooth Device Address (O) +TSPC_GAP_8A_16 False (*) AD Type –LE Role (O) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_SM_2_4 (OOB supported) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Broadcaster Connection Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_9_1 False (*) Broadcaster: Non-Connectable Mode +------------------------------------------------------------------------------- + + + Broadcaster Broadcasting and Observing Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_10_1 False (*) Broadcaster: Broadcast Mode +TSPC_GAP_11_1 False (*) Broadcaster: Privacy Feature v.1.0 +TSPC_GAP_11_1A False (*) Broadcaster: Privacy Feature v1.1 (O) +TSPC_GAP_11_2 False (*) Broadcaster: Resolvable Private Address + Generation Procedure +TSPC_GAP_11_3 False (*) Broadcaster: Non-Resolvable Private Address + Generation Procedure (O) +------------------------------------------------------------------------------- + + + Observer Physical Layer +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_12_1 False (*) Observer: Receiver +TSPC_GAP_12_2 False (*) Observer: Transmitter +------------------------------------------------------------------------------- + + + Observer Link Layer States +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_13_1 False (*) Observer: Standby +TSPC_GAP_13_2 False (*) Observer: Scanning +------------------------------------------------------------------------------- + + + Observer Link Layer Scanning Types +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_14_1 False (*) Observer: Passive Scanning +TSPC_GAP_14_2 False (*) Observer: Active Scanning +------------------------------------------------------------------------------- + + + Observer Connection Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_15_1 False (*) Observer: Non-Connectable Mode +------------------------------------------------------------------------------- + + + Observer Broadcasting and Observing Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_16_1 False (*) Observer: Observation Procedure +------------------------------------------------------------------------------- + + + Observer Privacy Feature +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_17_1 False (*) Observer: Privacy Feature v1.0 (O) +TSPC_GAP_17_1A False (*) Observer: Privacy Feature v1.1 (O) +TSPC_GAP_17_2 False (*) Observer: Non-Resolvable Private Address + Generation Procedure (C.1) +TSPC_GAP_17_3 False (*) Observer: Resolvable Private Address Resolution + Procedure (C.2) +TSPC_GAP_17_4 False (*) Observer: Resolvable Private Address Generation + Procedure (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_17_1 and TSPC_GAP_14_2 (Active Scanning) are + supported and TSPC_GAP_17_4 (Resolvable Private Address Generation + Procedure) is Not Supported; Optional if CSA3 or later and + TSPC_GAP_17_4 are supported, otherwise Excluded. +C.2: Optional if TSPC_GAP_17_1 is supported, otherwise Excluded. +C.3: Mandatory if CSA3 or later and TSPC_GAP_17_1 and TSPC_GAP_14_2 + (Active Scanning) are supported and TSPC_GAP_17_2 (Non-Resolvable + Private Address Generation Procedure) is not supported; Optional if + CSA3 or later and TSPC_GAP_17_2 (Non-Resolvable Private Address + Generation Procedure) are supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Peripheral Physical Layer +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_18_1 True Peripheral: Transmitter +TSPC_GAP_18_2 True Peripheral: Receiver +------------------------------------------------------------------------------- + + + Peripheral Link Layer States +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_19_1 True Peripheral: Standby +TSPC_GAP_19_2 True Peripheral: Advertising +TSPC_GAP_19_3 True Peripheral: Connection, Slave Role +------------------------------------------------------------------------------- + + + Peripheral Link Layer Advertising Event Types +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_20_1 True Peripheral: Connectable Undirected Event (C.1) +TSPC_GAP_20_2 True Peripheral: Connectable Directed Event (C.2) +TSPC_GAP_20_2A True Peripheral: Low Duty Directed Advertising (C.3) +TSPC_GAP_20_3 True Peripheral: Non-Connectable Undirected Event +TSPC_GAP_20_4 True Peripheral: Scannable Undirected Event +------------------------------------------------------------------------------- + + + Peripheral Link Layer Advertising Data Types +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_20A_1 False (*) AD Type-Service UUID (C.1) +TSPC_GAP_20A_2 True AD Type-Local Name (C.1) +TSPC_GAP_20A_3 True AD Type-Flags (C.2) +TSPC_GAP_20A_4 False (*) AD Type-Manufacturer Specific Data (C.1) +TSPC_GAP_20A_5 True AD Type-TX Power Level (C.1) +TSPC_GAP_20A_6 False (*) AD Type-Security Manager Out of Band (OOB) (C.3) +TSPC_GAP_20A_7 False (*) AD Type-Security manager TK Value (C.1) +TSPC_GAP_20A_8 False (*) AD Type-Slave Connection Interval Range (C.1) +TSPC_GAP_20A_9 False (*) AD Type-Service Solicitation (C.1) +TSPC_GAP_20A_10 False (*) AD Type-Service Data (C.1) +TSPC_GAP_20A_11 False (*) AD Type –Appearance (C.1) +TSPC_GAP_20A_12 False (*) AD Type-Public Target Address (C.1) +TSPC_GAP_20A_13 False (*) AD Type-Random Target Address (C.1) +TSPC_GAP_20A_14 False (*) AD Type-Advertising Interval (C.1) +TSPC_GAP_20A_15 False (*) AD Type-LE Bluetooth Device Address (C.1) +TSPC_GAP_20A_16 False (*) AD Type – LE Role (C.1) +------------------------------------------------------------------------------- +C.1: Optional if (TSPC_GAP_20_1 or TSPC_GAP_20_3 or TSPC_GAP_20_4) is + supported, otherwise Excluded. +C.2: Mandatory if TSPC_GAP_22_2 (Limited Discoverable Mode) or TSPC_GAP_22_3 + (General Discoverable Mode) is supported, otherwise Optional. +C.3: Optional if (TSPC_GAP_20_1 (Connectable Undirected Event) or TSPC_GAP_20_3 + (Non-Connectable Undirected Event) or TSPC_GAP_20_4 + (Scannable Undirected Event)) and TSPC_SM_2_4 (OOB supported) are + supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Peripheral Link Layer Control Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_21_1 True Peripheral: Connection Update Procedure (M) +TSPC_GAP_21_2 True Peripheral: Channel Map Update Procedure (M) +TSPC_GAP_21_3 True Peripheral: Encryption Procedure (O) +TSPC_GAP_21_4 True Peripheral: Feature Exchange Procedure (M) +TSPC_GAP_21_5 True Peripheral: Version Exchange Procedure (M) +TSPC_GAP_21_6 True Peripheral: Termination Procedure (M) +TSPC_GAP_21_7 True Peripheral: LE Ping Procedure (C.3) +TSPC_GAP_21_8 True Peripheral: Slave Initiated Feature Exchange + Procedure (C.4) +TSPC_GAP_21_9 True Peripheral: Connection Parameter Request + Procedure (C.5) +------------------------------------------------------------------------------- + + + Peripheral Discovery Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_22_1 True Peripheral: Non-Discoverable Mode (C.2) +TSPC_GAP_22_2 True Peripheral: Limited Discoverable Mode (C.1) +TSPC_GAP_22_3 True Peripheral: General Discoverable Mode (C.1) +TSPC_GAP_22_4 True Peripheral: Name Discovery Procedure (C.3) +------------------------------------------------------------------------------- +C.1: Optional if (TSPC_GAP_5_3 OR TSPC_GAP_42_2), otherwise Excluded. +C.2: Mandatory if (TSPC_GAP_5_3 or TSPC_GAP_42_1) is supported, + otherwise Excluded. +C.3: Optional if TSPC_GAP_5_3 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Peripheral Connection Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_23_1 True Peripheral: Non-Connectable Mode (C.1) +TSPC_GAP_23_2 True Peripheral: Directed Connectable Mode (O) +TSPC_GAP_23_3 True Peripheral: Undirected Connectable Mode (M) +TSPC_GAP_23_4 True Peripheral: Connection Parameter Update + Procedure (O) +TSPC_GAP_23_5 True Peripheral: Terminate Connection Procedure (M) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_5_3 (LE Only – Peripheral role) OR TSPC_GAP_42_3 + (BR/EDR/LE – Non-Connectable Mode) OR TSPC_GAP_42_4 + (BR/EDR/LE – Connectable Mode) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Peripheral Bonding Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_24_1 True Peripheral: Non-Bondable Mode (M) +TSPC_GAP_24_2 True Peripheral: Bondable Mode (C.1) +TSPC_GAP_24_3 True Peripheral: Bonding Procedure (C.2) +TSPC_GAP_24_4 True Peripheral: Multiple Bonds (C.3) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_GAP_5_3 (LE Only – Peripheral role) OR (TSPC_GAP_38_3 + (BR/EDR/LE – Peripheral role) AND NOT TSPC_GAP_42_6 (BR.EDR/LE - + Bondable Mode)) is supported, Mandatory if TSPC_GAP_42_6 + (BR/EDR/LE – Bondable Mode) is supported, otherwise Excluded. +C.2: Optional if TSPC_GAP_24_2 (Bondable Mode) is supported, otherwise Excluded +------------------------------------------------------------------------------- + + + Peripheral Security Aspects Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_25_1 True Peripheral: Security Mode (O) +TSPC_GAP_25_2 True Peripheral: Security Mode 2 (O) +TSPC_GAP_25_3 True Peripheral: Authentication Procedure (C.2) +TSPC_GAP_25_4 True Peripheral: Authorization Procedure (O) +TSPC_GAP_25_5 True Peripheral: Connection Data Signing Procedure + (O) +TSPC_GAP_25_6 True Peripheral: Authenticate Signed Data Procedure + (O) +TSPC_GAP_25_7 True Peripheral: Authenticated Pairing + (LE security mode 1 level 3) (C.1) +TSPC_GAP_25_8 True Peripheral: Unauthenticated Pairing + (LE security mode 1 level 2) (C.1) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_GAP_25_1 is supported, otherwise Excluded. +C.2: Mandatory if TSPC_GAP_0A_1 and TSPC_GAP_27_4 are supported, + otherwise Optional. +------------------------------------------------------------------------------- + + + Peripheral Privacy Feature +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_26_1 False (*) Peripheral: Privacy Feature v1.0 (O) +TSPC_GAP_26_1A True Peripheral: Privacy Feature v1.1 (O) +TSPC_GAP_26_2 True Peripheral: Non-Resolvable Private Address + Generation Procedure (C.1) +TSPC_GAP_26_3 True Peripheral: Resolvable Private Address + Generation Procedure (C.2) +TSPC_GAP_26_4 True Peripheral: Resolvable Private Address + Generation Procedure (C.4) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_GAP_26_1 is supported, otherwise Excluded. +C.2: Mandatory if TSPC_GAP_26_1 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Peripheral GAP Characteristics +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_27_1 True Peripheral: Device Name (M) +TSPC_GAP_27_2 True Peripheral: Appearance (M) +TSPC_GAP_27_3 False (*) Peripheral: Peripheral Privacy Flag (C.1) +TSPC_GAP_27_4 False (*) Peripheral: Reconnection Address (C.2) +TSPC_GAP_27_5 False (*) Peripheral: Peripheral Preferred Connection + Parameters (O) +TSPC_GAP_27_6 False (*) Peripheral: Writeable Device Name (O) +TSPC_GAP_27_7 False (*) Peripheral: Writeable Appearance (O) +TSPC_GAP_27_8 False (*) Peripheral: Writeable Peripheral Privacy Flag + (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_26_1 is supported, otherwise Excluded. +C.2: Optional if TSPC_GAP_26_1 and TSPC_GAP_27_3 are supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + Central Physical Layer +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_28_1 True Central: Transmitter (M) +TSPC_GAP_28_2 True Central: Receiver (M) +------------------------------------------------------------------------------- + + + Central Link Layer States +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_29_1 True Central: Standby (M) +TSPC_GAP_29_2 True Central: Scanning (M) +TSPC_GAP_29_3 True Central: Initiating (M) +TSPC_GAP_29_4 True Central: Connection, Master Role (M) +------------------------------------------------------------------------------- + + + Central Link Layer Scanning Types +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_30_1 True Central: Passive Scanning (O) +TSPC_GAP_30_2 True Central: Active Scanning (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory if (TSPC_GAP_5_4 or TSPC_GAP_38_4) is supported. + Optional if TSPC_GAP_30_1 and (TSPC_GAP_5_4 OR TSPC_GAP_38_4) + is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Central Link Layer Control Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_31_1 True Central: Connection Update Procedure (M) +TSPC_GAP_31_2 True Central: Channel Map Update Procedure (M) +TSPC_GAP_31_3 True Central: Encryption Procedure (O) +TSPC_GAP_31_4 True Central: Feature Exchange Procedure (M) +TSPC_GAP_31_5 True Central: Version Exchange Procedure (M) +TSPC_GAP_31_6 True Central: Termination Procedure (M) +TSPC_GAP_31_7 True Central: LE Ping Procedure (C.1) +TSPC_GAP_31_8 True Central: Slave Initiated Feature Exchange + Procedure (C.2) +TSPC_GAP_31_9 True Central: Connection Parameter Request Procedure + (C.3) +------------------------------------------------------------------------------- + + + Central Discovery Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_32_1 True Central: Limited Discovery Procedure (C.2) +TSPC_GAP_32_2 True Central: General Discovery Procedure (C.1) +TSPC_GAP_32_3 True Central: Name Discovery Procedure (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory if (TSPC_GAP_5_4 or TSPC_GAP_40_1) is supported, else Excluded. +C.2: Optional if (TSPC_GAP_5_4 or TSPC_GAP_40_2) is supported, + otherwise Excluded. +C.3: Optional if (TSPC_GAP_5_4 or TSPC_GAP_40_4) is supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + Central Connection Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_33_1 True Central: Auto Connection Establishment + Procedure (C.3) +TSPC_GAP_33_2 True Central: General Connection Establishment + Procedure (C.1) +TSPC_GAP_33_3 True Central: Selective Connection Establishment + Procedure (C.3) +TSPC_GAP_33_4 True Central: Direct Connection Establishment + Procedure (C.2) +TSPC_GAP_33_5 True Central: Connection Parameter Update Procedure + (C.2) +TSPC_GAP_33_6 True Central: Terminate Connection Procedure + (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if (TSPC_GAP_5_4 or TSPC_GAP_40_5) and TSPC_GAP_36_1 is + supported, otherwise Optional. +C.2: Mandatory if (TSPC_GAP_5_4 or TSPC_GAP_40_5) is supported, + otherwise Excluded. +C.3: Optional if (TSPC_GAP_5_4 or TSPC_GAP_40_5) is supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + Central Bonding Modes and Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_34_1 True Central: Non-Bondable Mode (C.1) +TSPC_GAP_34_2 True Central: Bondable Mode (C.2) +TSPC_GAP_34_3 True Central: Bonding Procedure (O) +------------------------------------------------------------------------------- +C.1: Mandatory if (TSPC_GAP_5_4 or 39/5) is supported, otherwise Excluded. +C.2: Optional if (TSPC_GAP_5_4 or 39/6) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Central Security Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_35_1 True Central: Security Mode 1 (O) +TSPC_GAP_35_2 True Central: Security Mode 2 (O) +TSPC_GAP_35_3 True Central: Authentication Procedure (O) +TSPC_GAP_35_4 True Central: Authorization Procedure (O) +TSPC_GAP_35_5 True Central: Connection Data Signing Procedure (O) +TSPC_GAP_35_6 True Central: Authenticate Signed Data Procedure (O) +TSPC_GAP_35_7 True Central: Authenticated Pairing + (LE security mode 1 level 3) (C.1) +TSPC_GAP_35_8 True Central: Unauthenticated Pairing + (LE security mode 1 level 2) (C.1) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_GAP_35_1 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Central Privacy Feature +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_36_1 False (*) Central: Privacy Feature v1.0 (C.2) +TSPC_GAP_36_1A True Central: Privacy Feature v1.1 (C.4) +TSPC_GAP_36_2 True Central: Non-Resolvable Private Address + Generation Procedure (C.1) +TSPC_GAP_36_3 True Central: Resolvable Private Address Resolution + Procedure (C.2) +TSPC_GAP_36_4 False (*) Central: Write to Privacy Characteristic + (Enable/Disable Privacy) (O) +TSPC_GAP_36_5 True Central: Resolvable Private Address Generation + Procedure (C.6) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_36_1 and TSPC_GAP_30_2 are supported, + otherwise Excluded. +C.2: Mandatory if TSPC_GAP_36_1 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Central GAP Characteristics +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_37_1 True Central: Device Name (M) +TSPC_GAP_37_2 True Central: Appearance (M) +------------------------------------------------------------------------------- + + + BR/EDR/LE Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_38_1 False (*) BR/EDR/LE: Broadcaster (C.1) +TSPC_GAP_38_2 False (*) BR/EDR/LE: Observer (C.1) +TSPC_GAP_38_3 True BR/EDR/LE: Peripheral (C.1) +TSPC_GAP_38_4 True BR/EDR/LE: Central (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined roles. +This table is applicable for BR/EDR/LE configurations. For LE-only +configurations, see 'LE Roles' table for role declarations. +------------------------------------------------------------------------------- + + + Central BR/EDR/LE Modes +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_39_1 True Central BR/EDR/LE: Non-Discoverable Mode (C.1) +TSPC_GAP_39_2 True Central BR/EDR/LE: Discoverable Mode (C.2) +TSPC_GAP_39_3 True Central BR/EDR/LE: Non-Connectable Mode (C.3) +TSPC_GAP_39_4 True Central BR/EDR/LE: Connectable Mode (M) +TSPC_GAP_39_5 True Central BR/EDR/LE: Non-Bondable Mode (C.4) +TSPC_GAP_39_6 True Central BR/EDR/LE: Bondable Mode (C.5) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_1_1 is supported over BR/EDR, otherwise Excluded. +C.2: Mandatory if (TSPC_GAP_1_2 or TSPC_GAP_1_3) is supported over BR/EDR, + otherwise Excluded. +C.3: Mandatory if TSPC_GAP_1_4 is supported over BR/EDR, otherwise Excluded. +C.4: Mandatory if TSPC_GAP_1_6 is supported over BR/EDR, otherwise Excluded. +C.5: Mandatory if TSPC_GAP_1_7 is supported over BR/EDR, otherwise Excluded. +------------------------------------------------------------------------------- + + + Central BR/EDR/LE Idle Mode Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_40_1 True Central BR/EDR/LE: General Discovery (C.1) +TSPC_GAP_40_2 True Central BR/EDR/LE: Limited Discovery (C.2) +TSPC_GAP_40_3 True Central BR/EDR/LE: Device Type Discovery (C.3) +TSPC_GAP_40_4 True Central BR/EDR/LE: Name Discovery (C.4) +TSPC_GAP_40_5 True Central BR/EDR/LE: Link Establishment (C.5) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_3_1 is supported over BR/EDR, otherwise Excluded. +C.2: Mandatory if TSPC_GAP_3_2 is supported over BR/EDR, otherwise Excluded. +C.3: Mandatory if (TSPC_GAP_3_1 or TSPC_GAP_3_2) is supported over BR/EDR, + otherwise Excluded. +C.4: Mandatory if TSPC_GAP_3_3 is supported over BR/EDR, otherwise Excluded. +C.5: Mandatory if (TSPC_GAP_4_1 or TSPC_GAP_4_2) is supported over BR/EDR, + otherwise Excluded. +------------------------------------------------------------------------------- + + + Central BR/EDR/LE Security Aspects +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_41_1 True Central BR/EDR/LE: Security Aspects (M) +------------------------------------------------------------------------------- + + + Peripheral BR/EDR/LE Modes +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_42_1 True Peripheral BR/EDR/LE: Non-Discoverable Mode + (See Spec) +TSPC_GAP_42_2 True Peripheral BR/EDR/LE: Discoverable Mode + (See Spec) +TSPC_GAP_42_3 True Peripheral BR/EDR/LE: Non-Connectable Mode + (See Spec) +TSPC_GAP_42_4 True Peripheral BR/EDR/LE: Connectable Mode (M) +TSPC_GAP_42_5 True Peripheral BR/EDR/LE: Non-Bondable Mode + (See Spec) +TSPC_GAP_42_6 True Peripheral BR/EDR/LE: Bondable Mode (See Spec) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_1_1 is supported over BR/EDR, otherwise Excluded. +C.2: Mandatory if (TSPC_GAP_1_2 or TSPC_GAP_1_3) is supported over BR/EDR, + otherwise Excluded. +C.3: Mandatory if TSPC_GAP_1_4 is supported over BR/EDR, otherwise Excluded. +C.4: Mandatory if TSPC_GAP_1_6 is supported over BR/EDR, otherwise Excluded. +C.5: Mandatory if TSPC_GAP_1_7 is supported over BR/EDR, otherwise Excluded. +------------------------------------------------------------------------------- + + + Peripheral BR/EDR/LE Security Aspects +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_43_1 True Peripheral BR/EDR/LE: Non-Discoverable Mode +------------------------------------------------------------------------------- + + + Central Simultaneous BR/EDR and LE Transports +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_44_1 True Central BR/EDR/LE: Simultaneous BR/EDR and LE + Transports – BR/EDR Slave to the same + device (O) +TSPC_GAP_44_2 True Central BR/EDR/LE: Simultaneous BR/EDR and LE + Transports – BR/EDR Master to the same + device (O) +------------------------------------------------------------------------------- + + + Peripheral Simultaneous BR/EDR and LE Transports +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_45_1 True Simultaneous BR/EDR and LE Transports – BR/EDR + Slave to the same device (C.1) +TSPC_GAP_45_2 True Simultaneous BR/EDR and LE Transports – BR/EDR + Master to the same device (C.1) +------------------------------------------------------------------------------- +C.1: Optional if ((SUM ICS 31/14 (Core Spec Version 4.1) or SUM ICS 31/15 +(Core Spec Version 4.1+HS)) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_1_1 True GATT Client Role (O) +TSPC_GATT_1_2 True GATT Server Role (O) +TSPC_SM_1_1 True Master Role (Initiator) +TSPC_SM_1_2 True Slave Role (Responder) +TSPC_SM_2_4 True OOB supported (O) +-------------------------------------------------------------------------------
diff --git a/android/pics-gatt.txt b/android/pics-gatt.txt new file mode 100644 index 0000000..90585f5 --- /dev/null +++ b/android/pics-gatt.txt
@@ -0,0 +1,326 @@ +GATT PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults + +M - mandatory +O - optional + + Generic Attribute Profile Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_1_1 True Generic Attribute Profile Client (C.1) +TSPC_GATT_1_2 True Generic Attribute Profile Server (C.2) +TSPC_GATT_1A_1 True Complete GATT client (C.3) +TSPC_GATT_1A_2 True Complete GATT server (C.4) +------------------------------------------------------------------------------- +C.1: Optional to support IF TSPC_GATT_2_2; else IF TSPC_GATT_2_1 it is mandatory + to support at least one of TSPC_GATT_1_1 OR TSPC_GATT_1_2 +C.2: Mandatory to support IF TSPC_GATT_2_2; else IF TSPC_GATT_2_1 it is + mandatory to support at least one of TSPC_GATT_1_1 OR TSPC_GATT_1_2 +C.3: Optional IF TSPC_GATT_1_1 is supported, otherwise Excluded +C.4: Optional IF TSPC_GATT_1_2 is supported, otherwise Excluded +------------------------------------------------------------------------------- + + + ATT Bearer Transport +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_2_1 True Attribute Protocol Supported over BR/EDR + (L2CAP fixed channel support) (C.1) +TSPC_GATT_2_2 True Attribute Protocol Supported over LE (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory IF (SUM ICS 12/2 OR SUM ICS 12/9) is supported, otherwise + Excluded +C.2: Mandatory IF (SUM ICS 12/7 OR SUM ICS 12/9) is supported, otherwise + Excluded +------------------------------------------------------------------------------- + + + + Generic Attribute Profile Support +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_3_1 True Client: Exchange MTU (C.2) +TSPC_GATT_3_2 True Client: Discover All Primary Services (C.1) +TSPC_GATT_3_3 True Client: Discover Primary Services Service + UUID (C.1) +TSPC_GATT_3_4 True Client: Find Included Services (C.1) +TSPC_GATT_3_5 True Client: Discover All characteristics of a + Service (C.1) +TSPC_GATT_3_6 True Client: Discover Characteristics by UUID (C.1) +TSPC_GATT_3_7 True Client: Discover All Characteristic Descriptors + (C.1) +TSPC_GATT_3_8 True Client: Read Characteristic Value (C.1) +TSPC_GATT_3_9 True Client: Read using Characteristic UUID (C.1) +TSPC_GATT_3_10 True Client: Read Long Characteristic Values (C.1) +TSPC_GATT_3_11 False (*) Client: Read Multiple Characteristic + Values (C.1) +TSPC_GATT_3_12 True Client: Write without Response (C.1) +TSPC_GATT_3_13 True Client: Signed Write Without Response (C.1) +TSPC_GATT_3_14 True Client: Write Characteristic Value (C.1) +TSPC_GATT_3_15 True Client: Write Long Characteristic Values (C.1) +TSPC_GATT_3_16 True Client: Characteristic Value Reliable + Writes (C.1) +TSPC_GATT_3_17 True Client: Notifications (C.1) +TSPC_GATT_3_18 True Client: Indications (M) +TSPC_GATT_3_19 True Client: Read Characteristic Descriptors (C.1) +TSPC_GATT_3_20 True Client: Read long Characteristic Descriptors + (C.1) +TSPC_GATT_3_21 True Client: Write Characteristic Descriptors (C.1) +TSPC_GATT_3_22 True Client: Write Long Characteristic Descriptors + (C.1) +TSPC_GATT_3_23 True Client: Service Changed Characteristic (M) +TSPC_GATT_3B_1 True Client: Primary Service Declaration (M) +TSPC_GATT_3B_2 True Client: Secondary Service Declaration (M) +TSPC_GATT_3B_3 True Client: Include Declaration (M) +TSPC_GATT_3B_4 True Client: Characteristic Declaration (M) +TSPC_GATT_3B_5 True Client: Characteristic Value Declaration (M) +TSPC_GATT_3B_6 True Client: Characteristic Extended Properties (M) +TSPC_GATT_3B_7 True Client: Characteristic User Description + Descriptor (M) +TSPC_GATT_3B_8 True Client: Client Characteristic Configuration + Descriptor (M) +TSPC_GATT_3B_9 True Client: Server Characteristic Configuration + Descriptor (M) +TSPC_GATT_3B_10 True Client: Characteristic Format Descriptor (M) +TSPC_GATT_3B_11 True Client: Characteristic Aggregate Format + Descriptor (M) +TSPC_GATT_3B_12 True Client: Characteristic Format: Boolean (M) +TSPC_GATT_3B_13 True Client: Characteristic Format: 2Bit (M) +TSPC_GATT_3B_14 True Client: Characteristic Format: nibble (M) +TSPC_GATT_3B_15 True Client: Characteristic Format: Uint8 (M) +TSPC_GATT_3B_16 True Client: Characteristic Format: Uint12 (M) +TSPC_GATT_3B_17 True Client: Characteristic Format: Uint16 (M) +TSPC_GATT_3B_18 True Client: Characteristic Format: Uint24 (M) +TSPC_GATT_3B_19 True Client: Characteristic Format: Uint32 (M) +TSPC_GATT_3B_20 True Client: Characteristic Format: Uint48 (M) +TSPC_GATT_3B_21 True Client: Characteristic Format: Uint64 (M) +TSPC_GATT_3B_22 True Client: Characteristic Format: Uint128 (M) +TSPC_GATT_3B_23 True Client: Characteristic Format: Sint8 (M) +TSPC_GATT_3B_24 True Client: Characteristic Format: Sint12 (M) +TSPC_GATT_3B_25 True Client: Characteristic Format: Sint16 (M) +TSPC_GATT_3B_26 True Client: Characteristic Format: Sint24 (M) +TSPC_GATT_3B_27 True Client: Characteristic Format: Sint32 (M) +TSPC_GATT_3B_28 True Client: Characteristic Format: Sint48 (M) +TSPC_GATT_3B_29 True Client: Characteristic Format: Sint64 (M) +TSPC_GATT_3B_30 True Client: Characteristic Format: Sint128 (M) +TSPC_GATT_3B_31 True Client: Characteristic Format: Float32 (M) +TSPC_GATT_3B_32 True Client: Characteristic Format: Float64 (M) +TSPC_GATT_3B_33 True Client: Characteristic Format: SFLOAT (M) +TSPC_GATT_3B_34 True Client: Characteristic Format: FLOAT (M) +TSPC_GATT_3B_35 True Client: Characteristic Format: Duint16 (M) +TSPC_GATT_3B_36 True Client: Characteristic Format: utf8s (M) +TSPC_GATT_3B_37 True Client: Characteristic Format: utf16s (M) +TSPC_GATT_3B_38 True Client: Characteristic Format: struct (M) +------------------------------------------------------------------------------- +C.1: Mandatory IF TSPC_GATT_1_3 is supported, otherwise Optional +C.2: Mandatory IF TSPC_GATT_1_3 AND TSPC_GATT_2_2 is supported, otherwise + Excluded +------------------------------------------------------------------------------- + + + + Generic Attribute Profile Support, by Server +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_4_1 True Server: Exchange MTU (C.4) +TSPC_GATT_4_2 True Server: Discover All Primary Services (M) +TSPC_GATT_4_3 True Server: Discover Primary Services Service + UUID (M) +TSPC_GATT_4_4 True Server: Find Included Services (M) +TSPC_GATT_4_5 True Server: Discover All characteristics of + a Service (M) +TSPC_GATT_4_6 True Server: Discover Characteristics by UUID (M) +TSPC_GATT_4_7 True Server: Discover All Characteristic + Descriptors (M) +TSPC_GATT_4_8 True Server: Read Characteristic Value (M) +TSPC_GATT_4_9 True Server: Read using Characteristic UUID (M) +TSPC_GATT_4_10 True Server: Read Long Characteristic Values (C.4) +TSPC_GATT_4_11 False (*) Server: Read Multiple Characteristic + Values (C.4) +TSPC_GATT_4_12 True Server: Write without Response (C.2) +TSPC_GATT_4_13 True Server: Signed Write Without Response (C.4) +TSPC_GATT_4_14 True Server: Write Characteristic Value (C.3) +TSPC_GATT_4_15 True Server: Write Long Characteristic Values (C.4) +TSPC_GATT_4_16 True Server: Characteristic Value Reliable + Writes (C.4) +TSPC_GATT_4_17 True Server: Notifications (C.4) +TSPC_GATT_4_18 True Server: Indications (C.1) +TSPC_GATT_4_19 True Server: Read Characteristic Descriptors (C.4) +TSPC_GATT_4_20 True Server: Read long Characteristic + Descriptors (C.4) +TSPC_GATT_4_21 True Server: Write Characteristic Descriptors (C.4) +TSPC_GATT_4_22 True Server: Write Long Characteristic + Descriptors (C.4) +TSPC_GATT_4_23 True Server: Service Changed Characteristic (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory IF service definitions on the server can be added, changed, or + removed, otherwise Optional +C.2: Mandatory IF GATT TSPC_GATT_4_13 is supported, otherwise Optional +C.3: Mandatory IF GATT TSPC_GATT_4_15 is supported, otherwise Optional +C.4: Mandatory IF GATT TSPC_GATT_1_4 is supported, otherwise Optional +------------------------------------------------------------------------------- + + + Profile Attribute Types and Characteristic Formats +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_4B_1 True Server: Primary Service Declaration (M) +TSPC_GATT_4B_2 True Server: Secondary Service Declaration (M) +TSPC_GATT_4B_3 True Server: Include Declaration (M) +TSPC_GATT_4B_4 True Server: Characteristic Declaration (M) +TSPC_GATT_4B_5 True Server: Characteristic Value Declaration (M) +TSPC_GATT_4B_6 True Server: Characteristic Extended Properties (M) +TSPC_GATT_4B_7 True Server: Characteristic User Description + Descriptor (M) +TSPC_GATT_4B_8 True Server: Client Characteristic Configuration + Descriptor (M) +TSPC_GATT_4B_9 True Server: Server Characteristic Configuration + Descriptor (M) +TSPC_GATT_4B_10 True Server: Characteristic Format Descriptor (M) +TSPC_G